From 2cfbfd8649e00e83c1bbe1f8588250e91c276410 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Apr 2016 20:00:20 +0200 Subject: [PATCH 001/206] Start of better budget charts. --- .../Budget/BudgetChartGeneratorInterface.php | 7 --- .../Budget/ChartJsBudgetChartGenerator.php | 19 ++----- app/Helpers/Report/BudgetReportHelper.php | 27 ++++++++++ .../Report/BudgetReportHelperInterface.php | 9 ++++ .../Controllers/Chart/BudgetController.php | 49 ------------------- app/Http/Controllers/ReportController.php | 7 +-- app/Http/routes.php | 2 - public/js/ff/reports/default/year.js | 13 ++++- resources/views/reports/default/year.twig | 6 ++- 9 files changed, 59 insertions(+), 80 deletions(-) diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php index 58d4e5302f..f9c2279646 100644 --- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php +++ b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php @@ -19,13 +19,6 @@ use Illuminate\Support\Collection; */ interface BudgetChartGeneratorInterface { - /** - * @param Collection $entries - * - * @return array - */ - public function budget(Collection $entries): array; - /** * @param Collection $entries * diff --git a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php index 3cdd335b31..9ba792f194 100644 --- a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php +++ b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php @@ -14,18 +14,16 @@ use Preferences; */ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface { - /** + * * @param Collection $entries - * @param string $dateFormat * * @return array */ - public function budget(Collection $entries, $dateFormat = 'month'): array + public function budgetLimit(Collection $entries): array { - // language: $language = Preferences::get('language', env('DEFAULT_LANGUAGE', 'en_US'))->data; - $format = Config::get('firefly.' . $dateFormat . '.' . $language); + $format = Config::get('firefly.monthAndDay.' . $language); $data = [ 'labels' => [], @@ -49,17 +47,6 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface return $data; } - /** - * - * @param Collection $entries - * - * @return array - */ - public function budgetLimit(Collection $entries): array - { - return $this->budget($entries, 'monthAndDay'); - } - /** * @param Collection $entries * diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index e7e8036fa2..35707945a2 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -14,6 +14,7 @@ namespace FireflyIII\Helpers\Report; use Carbon\Carbon; use FireflyIII\Helpers\Collection\Budget as BudgetCollection; use FireflyIII\Helpers\Collection\BudgetLine; +use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; use Illuminate\Support\Collection; @@ -106,6 +107,32 @@ class BudgetReportHelper implements BudgetReportHelperInterface return $object; } + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return Collection + */ + public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection + { + /** @var \FireflyIII\Repositories\Budget\BudgetRepositoryInterface $repository */ + $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $budgets = $repository->getActiveBudgets(); + + $set = new Collection; + /** @var Budget $budget */ + foreach ($budgets as $budget) { + $expenses = $repository->getExpensesPerDay($budget, $start, $end); + $total = strval($expenses->sum('dailyAmount')); + if (bccomp($total, '0') === -1) { + $set->push($budget); + } + } + + return $set; + } + /** * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay * and sum up everything in the array in the given range. diff --git a/app/Helpers/Report/BudgetReportHelperInterface.php b/app/Helpers/Report/BudgetReportHelperInterface.php index b1cd8fb955..6bc376f2e4 100644 --- a/app/Helpers/Report/BudgetReportHelperInterface.php +++ b/app/Helpers/Report/BudgetReportHelperInterface.php @@ -30,4 +30,13 @@ interface BudgetReportHelperInterface * @return BudgetCollection */ public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts): BudgetCollection; + + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return Collection + */ + public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection; } diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index b7025dd279..6c7ee6fe29 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -36,55 +36,6 @@ class BudgetController extends Controller $this->generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface'); } - /** - * @param BudgetRepositoryInterface $repository - * @param Budget $budget - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function budget(BudgetRepositoryInterface $repository, Budget $budget) - { - - // dates and times - $first = $repository->getFirstBudgetLimitDate($budget); - $range = Preferences::get('viewRange', '1M')->data; - $last = session('end', new Carbon); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($first); - $cache->addProperty($last); - $cache->addProperty('budget'); - if ($cache->has()) { - - return Response::json($cache->get()); - } - - $final = clone $last; - $final->addYears(2); - $last = Navigation::endOfX($last, $range, $final); - $entries = new Collection; - // get all expenses: - $spentArray = $repository->spentPerDay($budget, $first, $last); - - while ($first < $last) { - - // periodspecific dates: - $currentStart = Navigation::startOfPeriod($first, $range); - $currentEnd = Navigation::endOfPeriod($first, $range); - $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray); - $entry = [$first, ($spent * -1)]; - - $entries->push($entry); - $first = Navigation::addPeriod($first, $range, 0); - } - - $data = $this->generator->budget($entries); - $cache->store($data); - - return Response::json($data); - } - /** * Shows the amount left in a specific budget limit. * diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 0873d45846..ac269636e6 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -9,8 +9,6 @@ use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use Illuminate\Support\Collection; use Log; use Preferences; @@ -313,6 +311,9 @@ class ReportController extends Controller $expenses = $this->helper->getExpenseReport($start, $end, $accounts); $tags = $this->helper->tagReport($start, $end, $accounts); + // find the budgets we've spent money on this period with these accounts: + $budgets = $this->budgetHelper->getBudgetsWithExpenses($start, $end, $accounts); + Session::flash('gaEventCategory', 'report'); Session::flash('gaEventAction', 'year'); Session::flash('gaEventLabel', $start->format('Y')); @@ -329,7 +330,7 @@ class ReportController extends Controller 'reports.default.year', compact( 'start', 'accountReport', 'incomes', 'reportType', 'accountIds', 'end', - 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags' + 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags', 'budgets' ) ); } diff --git a/app/Http/routes.php b/app/Http/routes.php index c9caa03944..04b22c255c 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -200,9 +200,7 @@ Route::group( Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']); // this chart is used in reports: - Route::get('/chart/budget/year/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@year']); Route::get('/chart/budget/multi-year/{reportType}/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']); - Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']); Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']); diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js index 8a39be3ef3..184b651d37 100644 --- a/public/js/ff/reports/default/year.js +++ b/public/js/ff/reports/default/year.js @@ -18,7 +18,18 @@ function drawChart() { lineChart('chart/report/net-worth/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'net-worth'); columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart'); columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart'); - stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets'); + + // in a loop + $.each($('.budget_year_chart'), function (i, v) { + var holder = $(v); + var id = holder.id; + var budgetId = holder.data('budget'); + console.log('now at ' + id); + columnChart('chart/budget/period/' + budgetId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, id); + + }); + + //stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets'); stackedColumnChart('chart/category/spent-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-period'); stackedColumnChart('chart/category/earned-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-earned-in-period'); diff --git a/resources/views/reports/default/year.twig b/resources/views/reports/default/year.twig index 43b6b4e9be..83e189a86f 100644 --- a/resources/views/reports/default/year.twig +++ b/resources/views/reports/default/year.twig @@ -87,18 +87,20 @@ + {% for budget in budgets %}
-

{{ 'budgets'|_ }}

+

{{ budget.name }}

- +
+ {% endfor %} {% endblock %} {% block scripts %} From 32c8ddbe1bb02473381b574a501eb0413ae1e25f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Apr 2016 20:23:17 +0200 Subject: [PATCH 002/206] First render of new budget charts. --- .../Budget/BudgetChartGeneratorInterface.php | 9 ++++ .../Budget/ChartJsBudgetChartGenerator.php | 37 +++++++++++++ .../Controllers/Chart/BudgetController.php | 53 +++++++++++++++++++ app/Http/routes.php | 1 + app/Support/Navigation.php | 8 +-- public/js/ff/reports/default/year.js | 3 +- 6 files changed, 105 insertions(+), 6 deletions(-) diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php index f9c2279646..0d4e056673 100644 --- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php +++ b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php @@ -19,6 +19,7 @@ use Illuminate\Support\Collection; */ interface BudgetChartGeneratorInterface { + /** * @param Collection $entries * @@ -40,6 +41,14 @@ interface BudgetChartGeneratorInterface */ public function multiYear(Collection $entries): array; + /** + * @param Collection $entries + * @param string $viewRange + * + * @return array + */ + public function period(Collection $entries, string $viewRange) : array; + /** * @param Collection $budgets * @param Collection $entries diff --git a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php index 9ba792f194..7da18e57ac 100644 --- a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php +++ b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php @@ -5,6 +5,7 @@ namespace FireflyIII\Generator\Chart\Budget; use Config; use Illuminate\Support\Collection; +use Navigation; use Preferences; /** @@ -126,6 +127,42 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface } + /** + * @param Collection $entries + * @param string $viewRange + * + * @return array + */ + public function period(Collection $entries, string $viewRange) : array + { + $data = [ + 'labels' => [], + 'datasets' => [ + 0 => [ + 'label' => trans('firefly.budgeted'), + 'data' => [], + ], + 1 => [ + 'label' => trans('firefly.spent'), + 'data' => [], + ], + ], + 'count' => 2, + ]; + foreach ($entries as $entry) { + $label = Navigation::periodShow($entry['date'], $viewRange); + $data['labels'][] = $label; + // data set 0 is budgeted + // data set 1 is spent: + $data['datasets'][0]['data'][] = $entry['budgeted']; + $data['datasets'][1]['data'][] = round(($entry['spent'] * -1), 2); + + } + + return $data; + + } + /** * @param Collection $budgets * @param Collection $entries diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 6c7ee6fe29..e83dc4b108 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -240,6 +240,59 @@ class BudgetController extends Controller } + /** + * @param Budget $budget + * @param string $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + */ + public function period(Budget $budget, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($reportType); + $cache->addProperty($accounts); + $cache->addProperty('budget'); + $cache->addProperty('period'); + if ($cache->has()) { + //return Response::json($cache->get()); + } + + /** @var BudgetRepositoryInterface $repository */ + $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + // loop over period, add by users range: + $current = clone $start; + $viewRange = Preferences::get('viewRange', '1M')->data; + $set = new Collection; + while ($current < $end) { + $currentStart = clone $current; + $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); + + // get all budget limits and their repetitions. + $reps = $repository->getAllBudgetLimitRepetitions($currentStart, $currentEnd); + $budgeted = $reps->sum('amount'); + $perBudget = $repository->spentPerBudgetPerAccount(new Collection([$budget]), $accounts, $currentStart, $currentEnd); + $spent = $perBudget->sum('spent'); + + $entry = [ + 'date' => clone $currentStart, + 'budgeted' => $budgeted, + 'spent' => $spent, + ]; + $set->push($entry); + $currentEnd->addDay(); + $current = clone $currentEnd; + } + $data = $this->generator->period($set, $viewRange); + $cache->store($data); + + return Response::json($data); + + } + /** * * @param BudgetRepositoryInterface $repository diff --git a/app/Http/routes.php b/app/Http/routes.php index 04b22c255c..c8ab30ac4d 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -201,6 +201,7 @@ Route::group( // this chart is used in reports: Route::get('/chart/budget/multi-year/{reportType}/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']); + Route::get('/chart/budget/period/{budget}/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@period']); Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']); Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']); diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index 0d3777e93e..de8ee523c3 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -56,18 +56,18 @@ class Navigation } /** - * @param \Carbon\Carbon $theCurrentEnd + * @param \Carbon\Carbon $end * @param $repeatFreq * * @return \Carbon\Carbon * @throws FireflyException */ - public function endOfPeriod(Carbon $theCurrentEnd, string $repeatFreq): Carbon + public function endOfPeriod(Carbon $end, string $repeatFreq): Carbon { - $currentEnd = clone $theCurrentEnd; + $currentEnd = clone $end; $functionMap = [ - '1D' => 'addDay', 'daily' => 'addDay', + '1D' => 'endOfDay', 'daily' => 'endOfDay', '1W' => 'addWeek', 'week' => 'addWeek', 'weekly' => 'addWeek', '1M' => 'addMonth', 'month' => 'addMonth', 'monthly' => 'addMonth', '3M' => 'addMonths', 'quarter' => 'addMonths', 'quarterly' => 'addMonths', '6M' => 'addMonths', 'half-year' => 'addMonths', diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js index 184b651d37..30c8efaa84 100644 --- a/public/js/ff/reports/default/year.js +++ b/public/js/ff/reports/default/year.js @@ -22,9 +22,8 @@ function drawChart() { // in a loop $.each($('.budget_year_chart'), function (i, v) { var holder = $(v); - var id = holder.id; + var id = holder.attr('id'); var budgetId = holder.data('budget'); - console.log('now at ' + id); columnChart('chart/budget/period/' + budgetId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, id); }); From fe0b62b9b4473c4bf41570851d776f7618254f9b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Apr 2016 20:23:42 +0200 Subject: [PATCH 003/206] Cache charts. [skip ci] --- app/Http/Controllers/Chart/BudgetController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index e83dc4b108..6fbf4b8e19 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -258,7 +258,7 @@ class BudgetController extends Controller $cache->addProperty('budget'); $cache->addProperty('period'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } /** @var BudgetRepositoryInterface $repository */ From 89ff5a83b5a0bc4279eb1e1a740b5c052819cb8e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Apr 2016 20:25:35 +0200 Subject: [PATCH 004/206] Expand cache. [skip ci] --- app/Http/Controllers/Chart/BudgetController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 6fbf4b8e19..966e87d7d6 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -255,6 +255,7 @@ class BudgetController extends Controller $cache->addProperty($end); $cache->addProperty($reportType); $cache->addProperty($accounts); + $cache->addProperty($budget->id); $cache->addProperty('budget'); $cache->addProperty('period'); if ($cache->has()) { From ed863986a76acc204aaf10cc2fab3014bf496cef Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Apr 2016 20:28:08 +0200 Subject: [PATCH 005/206] Fine tuning. [skip ci] --- app/Helpers/Report/BudgetReportHelper.php | 2 +- app/Repositories/Budget/BudgetRepository.php | 38 +++++++++++-------- .../Budget/BudgetRepositoryInterface.php | 9 +++-- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 35707945a2..c5e3a3ea3c 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -123,7 +123,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface $set = new Collection; /** @var Budget $budget */ foreach ($budgets as $budget) { - $expenses = $repository->getExpensesPerDay($budget, $start, $end); + $expenses = $repository->getExpensesPerDay($budget, $start, $end, $accounts); $total = strval($expenses->sum('dailyAmount')); if (bccomp($total, '0') === -1) { $set->push($budget); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index bb2b0ee851..091e53f130 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -447,26 +447,32 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn * Returns the expenses for this budget grouped per day, with the date * in "date" (a string, not a Carbon) and the amount in "dailyAmount". * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return Collection */ - public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end): Collection + public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts = null): Collection { - $set = $this->user->budgets() - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id') - ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('budgets.id', $budget->id) - ->where('transactions.amount', '<', 0) - ->groupBy('transaction_journals.date') - ->orderBy('transaction_journals.date') - ->get(['transaction_journals.date', DB::raw('SUM(`transactions`.`amount`) as `dailyAmount`')]); + $query = $this->user->budgets() + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id') + ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('budgets.id', $budget->id) + ->where('transactions.amount', '<', 0) + ->groupBy('transaction_journals.date') + ->orderBy('transaction_journals.date'); + if (!is_null($accounts) && $accounts->count() > 0) { + $ids = $accounts->pluck('id')->toArray(); + $query->whereIn('transactions.account_id', $ids); + } + $set + = $query->get(['transaction_journals.date', DB::raw('SUM(`transactions`.`amount`) as `dailyAmount`')]); return $set; } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index b7ea891594..7bb87520b9 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -170,13 +170,14 @@ interface BudgetRepositoryInterface * Returns the expenses for this budget grouped per day, with the date * in "date" (a string, not a Carbon) and the amount in "dailyAmount". * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return Collection */ - public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end):Collection; + public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts = null) : Collection; /** * @param Budget $budget From 53760766a012c84a994870d4dd8c19a14c0141e0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Apr 2016 20:41:12 +0200 Subject: [PATCH 006/206] Fixed budget charts. --- .../Controllers/Chart/BudgetController.php | 15 ++++++++++-- app/Repositories/Budget/BudgetRepository.php | 23 ++++++++++++------- .../Budget/BudgetRepositoryInterface.php | 3 ++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 966e87d7d6..e4fca45a10 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -7,6 +7,7 @@ use Carbon\Carbon; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\CacheProperties; @@ -273,10 +274,20 @@ class BudgetController extends Controller $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); // get all budget limits and their repetitions. - $reps = $repository->getAllBudgetLimitRepetitions($currentStart, $currentEnd); + $reps = $repository->getAllBudgetLimitRepetitions($currentStart, $currentEnd, $budget); $budgeted = $reps->sum('amount'); $perBudget = $repository->spentPerBudgetPerAccount(new Collection([$budget]), $accounts, $currentStart, $currentEnd); - $spent = $perBudget->sum('spent'); + // includes null, so filter! + $perBudget = $perBudget->filter( + function (TransactionJournal $journal) use ($budget) { + if (intval($journal->budget_id) === $budget->id) { + return $journal; + } + } + ); + + + $spent = $perBudget->sum('spent'); $entry = [ 'date' => clone $currentStart, diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 091e53f130..f90d15065e 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -146,19 +146,26 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn /** * @param Carbon $start * @param Carbon $end + * @param Budget $budget * * @return Collection */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection + public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end, Budget $budget = null): Collection { - /** @var Collection $repetitions */ - return LimitRepetition:: + $query = LimitRepetition:: leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') - ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')) - ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) - ->where('budgets.user_id', $this->user->id) - ->get(['limit_repetitions.*', 'budget_limits.budget_id']); + ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')) + ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) + ->where('budgets.user_id', $this->user->id); + + if (!is_null($budget)) { + $query->where('budgets.id', $budget->id); + } + + $set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']); + + return $set; } /** diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 7bb87520b9..51b252ad8e 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -78,10 +78,11 @@ interface BudgetRepositoryInterface /** * @param Carbon $start * @param Carbon $end + * @param Budget $budget * * @return Collection */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection; + public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end, Budget $budget = null): Collection; /** * @param Account $account From ce5304277d536accd944ec43bf7adeb575632092 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 09:11:09 +0200 Subject: [PATCH 007/206] Translation for popup [skip ci] --- app/Http/Controllers/BudgetController.php | 14 ++++++++------ resources/lang/en_US/firefly.php | 1 + resources/views/budgets/income.twig | 4 +++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 0d79d6a3b8..03c165a877 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -336,13 +336,15 @@ class BudgetController extends Controller $range = Preferences::get('viewRange', '1M')->data; /** @var Carbon $date */ - $date = session('start', new Carbon); - $start = Navigation::startOfPeriod($date, $range); - $end = Navigation::endOfPeriod($start, $range); - $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); - $amount = Preferences::get($key, 1000); + $date = session('start', new Carbon); + $start = Navigation::startOfPeriod($date, $range); + $end = Navigation::endOfPeriod($start, $range); + $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); + $amount = Preferences::get($key, 1000); + $displayStart = Navigation::periodShow($start, $range); + $displayEnd = Navigation::periodShow($end, $range); - return view('budgets.income', compact('amount')); + return view('budgets.income', compact('amount', 'displayStart', 'displayEnd')); } } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 133b37a34d..fc3d38f165 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -471,6 +471,7 @@ return [ 'updated_budget' => 'Updated budget ":name"', 'update_amount' => 'Update amount', 'update_budget' => 'Update budget', + 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', // bills: 'matching_on' => 'Matching on', diff --git a/resources/views/budgets/income.twig b/resources/views/budgets/income.twig index 501dc93acc..aef412667c 100644 --- a/resources/views/budgets/income.twig +++ b/resources/views/budgets/income.twig @@ -3,7 +3,9 @@
From 950576d38b99729f3f2bc0e06c4e2a80469e3c89 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 09:12:52 +0200 Subject: [PATCH 008/206] Better date format [skip ci] --- app/Http/Controllers/BudgetController.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 03c165a877..7b0942d0ba 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -333,7 +333,8 @@ class BudgetController extends Controller */ public function updateIncome() { - $range = Preferences::get('viewRange', '1M')->data; + $range = Preferences::get('viewRange', '1M')->data; + $format = strval(trans('config.month_and_day')); /** @var Carbon $date */ $date = session('start', new Carbon); @@ -341,8 +342,8 @@ class BudgetController extends Controller $end = Navigation::endOfPeriod($start, $range); $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); $amount = Preferences::get($key, 1000); - $displayStart = Navigation::periodShow($start, $range); - $displayEnd = Navigation::periodShow($end, $range); + $displayStart = $start->formatLocalized($format); + $displayEnd = $end->formatLocalized($format); return view('budgets.income', compact('amount', 'displayStart', 'displayEnd')); } From ec70fde5575270dbd91a268a814e6dd526e6d96a Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 09:49:34 +0200 Subject: [PATCH 009/206] Start of some changes in budget overview related to #256 and #246 --- resources/views/budgets/index.twig | 40 +++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index b2b1b704da..bc06065233 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -99,10 +99,10 @@ - +

Bla bla bla

{% for budget in budgets %} -
+

@@ -130,7 +130,12 @@
- + - + + + + +
{{ 'budgeted'|_ }} + {{ 'budgeted'|_ }} +
+ {{ session('start').formatLocalized(monthAndDayFormat) }} - + {{ session('start').formatLocalized(monthAndDayFormat) }}
+
@@ -140,13 +145,40 @@ data-id="{{ budget.id }}" value="{{ budget.currentRep.amount|number_format(0,'','') }}" autocomplete="off" step="1" min="0" max="{{ budgetMaximum }}" name="amount" type="number">
+
+ + +
{{ 'spent'|_ }} + {{ 'spent'|_ }} +
+ {{ session('start').formatLocalized(monthAndDayFormat) }} - + {{ session('start').formatLocalized(monthAndDayFormat) }} +
+
{{ budget.spent|formatAmount }}
+ + Conflicts + + +
    +
  • Also budgeted {{ 123|formatAmount }} between x and y.
  • +
  • Also budgeted {{ 123|formatAmount }} between x and y.
  • +
+

From 2cd593157fc00dfc178feb355f3388da6998c135 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 09:50:46 +0200 Subject: [PATCH 010/206] Comment some stuff. --- resources/views/budgets/index.twig | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index bc06065233..dfa90e0f6e 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -134,7 +134,7 @@ {{ 'budgeted'|_ }}
{{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('start').formatLocalized(monthAndDayFormat) }}
+ {{ session('end').formatLocalized(monthAndDayFormat) }}
@@ -145,14 +145,15 @@ data-id="{{ budget.id }}" value="{{ budget.currentRep.amount|number_format(0,'','') }}" autocomplete="off" step="1" min="0" max="{{ budgetMaximum }}" name="amount" type="number">
+
@@ -161,11 +162,12 @@ {{ 'spent'|_ }}
{{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('start').formatLocalized(monthAndDayFormat) }} + {{ session('end').formatLocalized(monthAndDayFormat) }}
{{ budget.spent|formatAmount }} +
From 51d97cdca505ad205c694d11761ad5a64ee09705 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 09:57:39 +0200 Subject: [PATCH 011/206] Reinstated a chart. --- .../Budget/BudgetChartGeneratorInterface.php | 3 +- .../Budget/ChartJsBudgetChartGenerator.php | 5 +- .../Controllers/Chart/BudgetController.php | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php index 0d4e056673..fa418d3fd6 100644 --- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php +++ b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php @@ -22,10 +22,11 @@ interface BudgetChartGeneratorInterface /** * @param Collection $entries + * @param string $dateFormat * * @return array */ - public function budgetLimit(Collection $entries): array; + public function budgetLimit(Collection $entries, string $dateFormat): array; /** * @param Collection $entries diff --git a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php index 7da18e57ac..9d4adf812d 100644 --- a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php +++ b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php @@ -18,13 +18,14 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface /** * * @param Collection $entries + * @param string $dateFormat * * @return array */ - public function budgetLimit(Collection $entries): array + public function budgetLimit(Collection $entries, string $dateFormat = 'monthAndDay'): array { $language = Preferences::get('language', env('DEFAULT_LANGUAGE', 'en_US'))->data; - $format = Config::get('firefly.monthAndDay.' . $language); + $format = Config::get('firefly.' . $dateFormat . '.' . $language); $data = [ 'labels' => [], diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index e4fca45a10..dbabcbae5c 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -37,6 +37,55 @@ class BudgetController extends Controller $this->generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface'); } + /** + * @param BudgetRepositoryInterface $repository + * @param Budget $budget + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function budget(BudgetRepositoryInterface $repository, Budget $budget) + { + + // dates and times + $first = $repository->getFirstBudgetLimitDate($budget); + $range = Preferences::get('viewRange', '1M')->data; + $last = session('end', new Carbon); + + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($first); + $cache->addProperty($last); + $cache->addProperty('budget'); + if ($cache->has()) { + + return Response::json($cache->get()); + } + + $final = clone $last; + $final->addYears(2); + $last = Navigation::endOfX($last, $range, $final); + $entries = new Collection; + // get all expenses: + $spentArray = $repository->spentPerDay($budget, $first, $last); + + while ($first < $last) { + + // periodspecific dates: + $currentStart = Navigation::startOfPeriod($first, $range); + $currentEnd = Navigation::endOfPeriod($first, $range); + $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray); + $entry = [$first, ($spent * -1)]; + + $entries->push($entry); + $first = Navigation::addPeriod($first, $range, 0); + } + + $data = $this->generator->budgetLimit($entries, 'month'); + $cache->store($data); + + return Response::json($data); + } + /** * Shows the amount left in a specific budget limit. * From b4f18dbe774149b80cd73efa86fc90c92cf9f6e7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 11:44:41 +0200 Subject: [PATCH 012/206] Database stuff. --- app/Support/Migration/TestData.php | 2 +- .../2016_04_25_093451_changes_for_385.php | 39 +++++++++++++++++++ database/seeds/TestDataSeeder.php | 21 ++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2016_04_25_093451_changes_for_385.php diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index 66f22d01c2..b79ef52e2c 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -225,7 +225,7 @@ class TestData Budget::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); // some empty budgets. - foreach (['A', 'B', 'C', 'D', 'E'] as $letter) { + foreach (['A'] as $letter) { Budget::firstOrCreateEncrypted(['name' => 'Empty budget ' . $letter, 'user_id' => $user->id]); } diff --git a/database/migrations/2016_04_25_093451_changes_for_385.php b/database/migrations/2016_04_25_093451_changes_for_385.php new file mode 100644 index 0000000000..94d87cbd9f --- /dev/null +++ b/database/migrations/2016_04_25_093451_changes_for_385.php @@ -0,0 +1,39 @@ +dropUnique('unique_limit'); + } + ); + + // create it again, correctly. + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->unique(['budget_id', 'startdate','repeat_freq'], 'unique_limit'); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index f018ad6b6e..ed6151f9e2 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -9,6 +9,7 @@ declare(strict_types = 1); */ use Carbon\Carbon; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Support\Migration\TestData; use Illuminate\Database\Seeder; @@ -90,5 +91,25 @@ class TestDataSeeder extends Seeder $current->addMonth(); } + + // create some special budget limits to test stuff with multiple budget limits + // for a range of dates: + $this->end->startOfMonth(); + $budget = TestData::findBudget($user, 'Bills'); + $ranges = ['daily','weekly','monthly','quarterly','half-year','yearly']; + foreach($ranges as $range) { + BudgetLimit::create( + [ + 'budget_id' => $budget->id, + 'startdate' => $this->end->format('Y-m-d'), + 'amount' => rand(100,200), + 'repeats' => 0, + 'repeat_freq' => $range, + ] + ); + $this->end->addDay(); + } + + // b } } From 607d0115f00e43734ccd8ff9a17d472f5d62d763 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Apr 2016 13:20:42 +0200 Subject: [PATCH 013/206] Code improvements for budgets. --- app/Http/Controllers/BudgetController.php | 33 +++++++---- app/Repositories/Budget/BudgetRepository.php | 44 +++++++++++++- .../Budget/BudgetRepositoryInterface.php | 15 ++++- resources/lang/en_US/firefly.php | 3 + resources/views/budgets/index.twig | 57 +++++++++++-------- 5 files changed, 114 insertions(+), 38 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 7b0942d0ba..fe7e001209 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -3,6 +3,7 @@ use Amount; use Auth; use Carbon\Carbon; +use Config; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\BudgetFormRequest; use FireflyIII\Models\Budget; @@ -142,18 +143,22 @@ class BudgetController extends Controller */ public function index(BudgetRepositoryInterface $repository, ARI $accountRepository) { - $budgets = $repository->getActiveBudgets(); - $inactive = $repository->getInactiveBudgets(); - $spent = '0'; - $budgeted = '0'; - $range = Preferences::get('viewRange', '1M')->data; + $budgets = $repository->getActiveBudgets(); + $inactive = $repository->getInactiveBudgets(); + $spent = '0'; + $budgeted = '0'; + $range = Preferences::get('viewRange', '1M')->data; + $repeatFreq = Config::get('firefly.range_to_repeat_freq.' . $range); /** @var Carbon $date */ - $date = session('start', new Carbon); - $start = Navigation::startOfPeriod($date, $range); - $end = Navigation::endOfPeriod($start, $range); + /** @var Carbon $start */ + $start = session('start', new Carbon); + /** @var Carbon $end */ + $end = session('end', new Carbon); $key = 'budgetIncomeTotal' . $start->format('Ymd') . $end->format('Ymd'); $budgetIncomeTotal = Preferences::get($key, 1000)->data; $period = Navigation::periodShow($start, $range); + $periodStart = $start->formatLocalized($this->monthAndDayFormat); + $periodEnd = $end->formatLocalized($this->monthAndDayFormat); $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); /** @@ -164,8 +169,9 @@ class BudgetController extends Controller // loop the budgets: /** @var Budget $budget */ foreach ($budgets as $budget) { - $budget->spent = $repository->balanceInPeriod($budget, $start, $end, $accounts); - $budget->currentRep = $repository->getCurrentRepetition($budget, $start, $end); + $budget->spent = $repository->balanceInPeriod($budget, $start, $end, $accounts); + $budget->currentRep = $repository->getCurrentRepetition($budget, $repeatFreq, $start, $end); + $budget->otherRepetitions = $repository->getValidRepetitions($budget, $start, $end, $budget->currentRep); if (!is_null($budget->currentRep->id)) { $budgeted = bcadd($budgeted, $budget->currentRep->amount); } @@ -178,7 +184,12 @@ class BudgetController extends Controller $defaultCurrency = Amount::getDefaultCurrency(); return view( - 'budgets.index', compact('budgetMaximum', 'period', 'range', 'budgetIncomeTotal', 'defaultCurrency', 'inactive', 'budgets', 'spent', 'budgeted') + 'budgets.index', compact( + 'budgetMaximum', 'periodStart', 'periodEnd', + 'period', 'range', 'budgetIncomeTotal', + 'defaultCurrency', 'inactive', 'budgets', + 'spent', 'budgeted' + ) ); } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index f90d15065e..232cba03de 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -408,14 +408,16 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn /** * @param Budget $budget + * @param string $repeatFreq * @param Carbon $start * @param Carbon $end * * @return LimitRepetition */ - public function getCurrentRepetition(Budget $budget, Carbon $start, Carbon $end): LimitRepetition + public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition { $data = $budget->limitrepetitions() + ->where('budget_limits.repeat_freq', $repeatFreq) ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00')) ->first(['limit_repetitions.*']); @@ -844,4 +846,44 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $limit; } + + /** + * Returns a list of budget limits that are valid in the current given range. + * $ignore is optional. Send an empty limit rep. + * + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param LimitRepetition $ignore + * + * @return Collection + */ + public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection + { + $query = $budget->limitrepetitions() + ->where( // valid when either of these are true: + function ($q) use ($start, $end) { + $q->where( + function ($query) use ($start, $end) { + // starts before start time, and the end also after start time. + $query->where('limit_repetitions.startdate', '<=', $start->format('Y-m-d 00:00:00')); + $query->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')); + } + ); + $q->orWhere( + function ($query) use ($start, $end) { + // end after end time, and start is before end time + $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); + $query->where('limit_repetitions.enddate', '>=', $end->format('Y-m-d 00:00:00')); + } + ); + } + ); + if (!is_null($ignore->id)) { + $query->where('limit_repetitions.id', '!=', $ignore->id); + } + $data = $query->get(['limit_repetitions.*']); + + return $data; + } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 51b252ad8e..5671c6860b 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -146,14 +146,27 @@ interface BudgetRepositoryInterface */ public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection; + /** + * Returns a list of budget limits that are valid in the current given range. + * + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param LimitRepetition $ignore + * + * @return Collection + */ + public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection; + /** * @param Budget $budget + * @param string $repeatFreq * @param Carbon $start * @param Carbon $end * * @return LimitRepetition */ - public function getCurrentRepetition(Budget $budget, Carbon $start, Carbon $end): LimitRepetition; + public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition; /** * Returns all expenses for the given budget and the given accounts, in the given period. diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index fc3d38f165..4fd6ca302f 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -459,8 +459,11 @@ return [ 'store_new_budget' => 'Store new budget', 'stored_new_budget' => 'Stored new budget ":name"', 'availableIn' => 'Available in :date', + 'available_between' => 'Available between :start and :end', 'transactionsWithoutBudget' => 'Expenses without budget', 'transactionsWithoutBudgetDate' => 'Expenses without budget in :date', + 'transactions_no_budget' => 'Expenses without budget between :start and :end', + 'spent_between' => 'Spent between :start and :end', 'createBudget' => 'New budget', 'inactiveBudgets' => 'Inactive budgets', 'without_budget_between' => 'Transactions without a budget between :start and :end', diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index dfa90e0f6e..cfc1363153 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -9,7 +9,7 @@
-

{{ Session.get('start').formatLocalized(monthFormat) }}

+

{{ periodStart }} — {{ periodEnd }}

@@ -23,7 +23,7 @@ {{ 'budgeted'|_ }}: {{ budgeted|formatAmountPlain }}
- {{ trans('firefly.availableIn',{date : period }) }}: + {{ trans('firefly.available_between',{start : periodStart, end: periodEnd }) }}: {{ budgetIncomeTotal|formatAmount }} @@ -44,7 +44,7 @@
- {{ 'spent'|_ }}: {{ spent|formatAmount }} + {{ trans('firefly.spent_between', {start: periodStart, end: periodEnd}) }}: {{ spent|formatAmount }}
-

Bla bla bla

{% for budget in budgets %}
@@ -132,7 +131,7 @@ {{ 'budgeted'|_ }} -
+
{{ session('start').formatLocalized(monthAndDayFormat) }} - {{ session('end').formatLocalized(monthAndDayFormat) }}
@@ -160,35 +159,43 @@ {{ 'spent'|_ }} -
+
{{ session('start').formatLocalized(monthAndDayFormat) }} - {{ session('end').formatLocalized(monthAndDayFormat) }}
{{ budget.spent|formatAmount }} - + {% if budget.otherRepetitions.count > 0 %} + + +
    + {% for other in budget.otherRepetitions %} + {% if other.id != budget.currentRep.id %} +
  • Budgeted + {{ other.amount|formatAmountPlain }} + between + {{ other.startdate.formatLocalized(monthAndDayFormat) }} + and {{ other.enddate.formatLocalized(monthAndDayFormat) }}. +
  • + {% endif %} + {% endfor %} +
+ + + {% endif %}
+ {% if loop.index % 3 == 0 %} +
+ {% endif %} {% endfor %} +
- {% if inactive|length > 0 %} + {% if inactive|length > 0 %} +
@@ -209,8 +216,8 @@
- {% endif %} -
+
+ {% endif %} {% endblock %} {% block scripts %} + {% block scripts %}{% endblock %} diff --git a/resources/views/reports/default/year.twig b/resources/views/reports/default/year.twig index f8d4040337..85f951869c 100644 --- a/resources/views/reports/default/year.twig +++ b/resources/views/reports/default/year.twig @@ -8,7 +8,7 @@
-
+

{{ 'incomeVsExpenses'|_ }}

@@ -18,7 +18,7 @@
-
+

{{ 'incomeVsExpenses'|_ }}

@@ -61,42 +61,17 @@
- - - - {% for category in categories %}
-
+

{{ 'category'|_ }} {{ category.name }}

+ + +
+ +
-
+

{{ 'budget'|_ }} {{ budget.name }}

+ + +
+ +
Date: Fri, 29 Apr 2016 09:56:50 +0200 Subject: [PATCH 046/206] Translations. [skip ci] --- resources/lang/fr_FR/firefly.php | 6 ++ resources/lang/nl_NL/firefly.php | 6 ++ resources/lang/pt_BR/firefly.php | 178 ++++++++++++++++--------------- resources/lang/pt_BR/form.php | 8 +- resources/lang/pt_BR/help.php | 136 +++++++++++------------ 5 files changed, 176 insertions(+), 158 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index c14f07ff81..3b405eaf1e 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -257,6 +257,7 @@ return [ 'pref_1M' => 'One month', 'pref_3M' => 'Three months (quarter)', 'pref_6M' => 'Six months', + 'pref_1Y' => 'One year', 'pref_languages' => 'Languages', 'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?', 'pref_custom_fiscal_year' => 'Fiscal year settings', @@ -279,6 +280,7 @@ return [ 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', 'transaction_page_size_label' => 'Page size', 'budget_maximum' => 'Budget maximum', + 'between_dates' => '(:start and :end)', // profile: 'change_your_password' => 'Change your password', @@ -459,8 +461,11 @@ return [ 'store_new_budget' => 'Store new budget', 'stored_new_budget' => 'Stored new budget ":name"', 'availableIn' => 'Available in :date', + 'available_between' => 'Available between :start and :end', 'transactionsWithoutBudget' => 'Expenses without budget', 'transactionsWithoutBudgetDate' => 'Expenses without budget in :date', + 'transactions_no_budget' => 'Expenses without budget between :start and :end', + 'spent_between' => 'Spent between :start and :end', 'createBudget' => 'New budget', 'inactiveBudgets' => 'Inactive budgets', 'without_budget_between' => 'Transactions without a budget between :start and :end', @@ -471,6 +476,7 @@ return [ 'updated_budget' => 'Updated budget ":name"', 'update_amount' => 'Update amount', 'update_budget' => 'Update budget', + 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', // bills: 'matching_on' => 'Matching on', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 332aec8d35..313b257a74 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -257,6 +257,7 @@ return [ 'pref_1M' => 'Eén maand', 'pref_3M' => 'Drie maanden (kwartaal)', 'pref_6M' => 'Zes maanden', + 'pref_1Y' => 'Eén jaar', 'pref_languages' => 'Talen', 'pref_languages_help' => 'Firefly III ondersteunt meerdere talen. Welke heeft jouw voorkeur?', 'pref_custom_fiscal_year' => 'Instellingen voor boekjaar', @@ -279,6 +280,7 @@ return [ 'transaction_page_size_help' => 'Elke lijst met transacties er op is zo lang', 'transaction_page_size_label' => 'Paginalengte', 'budget_maximum' => 'Maximale budgetgrootte', + 'between_dates' => '(:start en :end)', // profile: 'change_your_password' => 'Verander je wachtwoord', @@ -459,8 +461,11 @@ return [ 'store_new_budget' => 'Sla nieuw budget op', 'stored_new_budget' => 'Nieuw budget ":name" opgeslagen', 'availableIn' => 'Beschikbaar in :date', + 'available_between' => 'Beschikbaar tussen :start en :end', 'transactionsWithoutBudget' => 'Uitgaven zonder budget', 'transactionsWithoutBudgetDate' => 'Uitgaven zonder budget in :date', + 'transactions_no_budget' => 'Uitgaven zonder budget tussen :start en :end', + 'spent_between' => 'Uitgegeven tussen :start en :end', 'createBudget' => 'Maak nieuw budget', 'inactiveBudgets' => 'Inactieve budgetten', 'without_budget_between' => 'Transacties zonder budget tussen :start en :end', @@ -471,6 +476,7 @@ return [ 'updated_budget' => 'Budget ":name" geüpdatet', 'update_amount' => 'Bedrag bijwerken', 'update_budget' => 'Budget bijwerken', + 'update_budget_amount_range' => 'Update het verwacht beschikbare bedrag tussen :start en :end', // bills: 'matching_on' => 'Wordt herkend', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index ba0b6bd44c..5162396bca 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -31,8 +31,8 @@ return [ 'bounced_error' => 'A mensagem enviado para :email ressaltado, não tem acesso para você.', 'deleted_error' => 'Estas credenciais não correspondem aos nossos registros.', 'general_blocked_error' => 'Sua conta foi desativada, você não pode entrar.', - 'expired_error' => 'Your account has expired, and can no longer be used.', - 'unbalanced_error' => 'Your transactions are unbalanced. This means a withdrawal, deposit or transfer was not stored properly. Please check your accounts and transactions for errors (unbalanced amount :amount).', + 'expired_error' => 'Sua conta expirou e não pode mais ser usada.', + 'unbalanced_error' => 'Suas operações estão desequilibrados. Isto significa uma retirada, depósito ou transferência não foi armazenado corretamente. Por favor verifique suas contas e transações por erros (quantidade desequilibrada :amount).', 'removed_amount' => ':amount removido', 'added_amount' => ':amount adicionada', 'asset_account_role_help' => 'Quaisquer opções extras resultantes da sua escolha pode ser definido mais tarde.', @@ -69,10 +69,10 @@ return [ 'warning_much_data' => ':days dias de dados podem demorar um pouco para carregar.', 'registered' => 'Você se registrou com sucesso!', 'search' => 'Pesquisa', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'no_budget_pointer' => 'Parece que não há orçamentos ainda. Você deve criar alguns na página orçamentos. Orçamentos podem ajudá-lo a manter o controle de despesas.', // repeat frequencies: - 'repeat_freq_monthly' => 'monthly', + 'repeat_freq_monthly' => 'mensal', 'weekly' => 'Semanal', 'quarterly' => 'Trimestral', 'half-year' => 'Semestral', @@ -123,7 +123,7 @@ return [ // rules 'rules' => 'Regras', - 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', + 'rules_explanation' => 'Aqui você pode gerenciar as regras. As regras são acionadas quando uma transação é criada ou atualizada. Então, se a transação tiver certas propriedades (chamadas de "gatilhos") Firefly executará as "ações". Combinado, você pode fazer Firefly responder de uma certa maneira para novas transações.', 'rule_name' => 'Nome da regra', 'rule_triggers' => 'Regra dispara quando', 'rule_actions' => 'Regra será', @@ -169,36 +169,36 @@ return [ 'edit_rule' => 'Editar regra ":title"', 'delete_rule' => 'Excluir a regra ":title"', 'update_rule' => 'Atualizar Regra', - 'test_rule_triggers' => 'Veja transações equivalentes', - 'warning_transaction_subset' => 'For performance reasons this list is limited to :max_num_transactions and may only show a subset of matching transactions', - 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.', + 'test_rule_triggers' => 'Veja transações correspondentes', + 'warning_transaction_subset' => 'Por razões de desempenho esta lista está limitado a :max_num_transactions e só pode mostrar um subconjunto das transações correspondentes', + 'warning_no_matching_transactions' => 'Nenhuma transação correspondente encontrada. Por favor note que por motivos de desempenho, apenas as últimas :num_transactions transações tenham sido verificadas.', 'warning_no_valid_triggers' => 'Sem gatilhos válidos fornecidos.', 'execute_on_existing_transactions' => 'Executar transações existentes', 'execute_on_existing_transactions_intro' => 'Quando uma regra ou um grupo for alterado ou adicionado, você pode executá-lo para transações existentes', 'execute_on_existing_transactions_short' => 'Transações existentes', - 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions', - 'execute_group_on_existing_transactions' => 'Execute group ":title" for existing transactions', - 'include_transactions_from_accounts' => 'Include transactions from these accounts', + 'executed_group_on_existing_transactions' => 'Executado o grupo ":title" para transações existentes', + 'execute_group_on_existing_transactions' => 'Executar o grupo ":title" para transações existentes', + 'include_transactions_from_accounts' => 'Incluir as transações destas contas', 'execute' => 'Executar', // actions and triggers 'rule_trigger_user_action' => 'Ação do usuário é ":trigger_value"', - 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', - 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', - 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', - 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', - 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', - 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', - 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', - 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', - 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', - 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', + 'rule_trigger_from_account_starts' => 'Conta de origem começa com ":trigger_value"', + 'rule_trigger_from_account_ends' => 'Conta de origem termina com ":trigger_value"', + 'rule_trigger_from_account_is' => 'É da conta de origem ":trigger_value"', + 'rule_trigger_from_account_contains' => 'Conta de origem contém ":trigger_value"', + 'rule_trigger_to_account_starts' => 'Conta destino começa com ":trigger_value"', + 'rule_trigger_to_account_ends' => 'Conta destino termina com ":trigger_value"', + 'rule_trigger_to_account_is' => 'Conta de destino é ":trigger_value"', + 'rule_trigger_to_account_contains' => 'Conta de destino contém ":trigger_value"', + 'rule_trigger_transaction_type' => 'Transação é do tipo ":trigger_value"', + 'rule_trigger_amount_less' => 'Quantia é inferior :trigger_value', 'rule_trigger_amount_exactly' => 'Quantia é: trigger_value', - 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', - 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', - 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', - 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', - 'rule_trigger_description_is' => 'Description is ":trigger_value"', + 'rule_trigger_amount_more' => 'Quantia é mais de :trigger_value', + 'rule_trigger_description_starts' => 'Descrição começa com ":trigger_value"', + 'rule_trigger_description_ends' => 'Descrição termina com ":trigger_value"', + 'rule_trigger_description_contains' => 'Descrição contém ":trigger_value"', + 'rule_trigger_description_is' => 'Descrição é ":trigger_value"', 'rule_trigger_from_account_starts_choice' => 'Conta de origem começa com..', 'rule_trigger_from_account_ends_choice' => 'Conta de origem termina com..', 'rule_trigger_from_account_is_choice' => 'Conta de origem é..', @@ -257,6 +257,7 @@ return [ 'pref_1M' => 'Um mês', 'pref_3M' => 'Trimestral', 'pref_6M' => 'Semestral', + 'pref_1Y' => 'One year', 'pref_languages' => 'Idiomas', 'pref_languages_help' => 'Firefly III suporta muitos idiomas. Qual você prefere?', 'pref_custom_fiscal_year' => 'Configurações de ano fiscal', @@ -275,10 +276,11 @@ return [ 'pref_two_factor_auth_remove_will_disable' => '(isso também irá desativar a autenticação de duas etapas)', 'pref_save_settings' => 'Salvar definições', 'saved_preferences' => 'Preferências salvas!', - 'transaction_page_size_title' => 'Page size', - 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', - 'transaction_page_size_label' => 'Page size', - 'budget_maximum' => 'Budget maximum', + 'transaction_page_size_title' => 'Tamanho da página', + 'transaction_page_size_help' => 'Qualquer lista de transações mostra, no máximo, muitas transações', + 'transaction_page_size_label' => 'Tamanho da página', + 'budget_maximum' => 'Máximo de orçamento', + 'between_dates' => '(:start and :end)', // profile: 'change_your_password' => 'Alterar sua senha', @@ -338,7 +340,7 @@ return [ 'csv_csv_config_file_help' => 'Selecione a configuração de importação CSV aqui. Se você não sabe o que é isso, ignore. Será explicado mais tarde.', 'csv_upload_button' => 'Iniciando importação do CSV', 'csv_column_roles_title' => 'Definir papeis da coluna', - 'csv_column_roles_text' => 'Firefly does not know what each column means. You need to indicate what every column is. Please check out the example data if you\'re not sure yourself. Click on the question mark (top right of the page) to learn what each column means. If you want to map imported data onto existing data in Firefly, use the checkbox. The next step will show you what this button does.', + 'csv_column_roles_text' => 'Firefly não sabe o que significa cada coluna. Você precisa indicar o que cada coluna é. Por favor confira os dados de exemplo se você não está certo mesmo. Clique sobre o ponto de interrogação (superior direito da página) para aprender o que significa que cada coluna. Se você deseja mapear dados importados para dados existentes em Firefly, use a caixa de seleção. O próximo passo irá mostrar-lhe o que esse botão faz.', 'csv_column_roles_table' => 'Papéis da Coluna', 'csv_column' => 'Coluna CSV', 'csv_column_name' => 'Nome da coluna do CSV', @@ -348,20 +350,20 @@ return [ 'csv_continue' => 'Continuar para o próximo passo', 'csv_go_back' => 'Voltar para o passo anterior', 'csv_map_title' => 'Valores mapeados encontrados para valores existentes', - 'csv_map_text' => 'This page allows you to map the values from the CSV file to existing entries in your database. This ensures that accounts and other things won\'t be created twice.', + 'csv_map_text' => 'Esta página permite mapear os valores do arquivo CSV para entradas existentes em seu banco de dados. Isso garante que as contas e outras coisas não serão criadas duas vezes.', 'csv_field_value' => 'Valor do campo do CSV', 'csv_field_mapped_to' => 'Deve ser mapeado para...', 'csv_do_not_map' => 'Não mapear este valor', 'csv_download_config_title' => 'Download do CSV de configuração ', - 'csv_download_config_text' => 'Everything you\'ve just set up can be downloaded as a configuration file. Click the button to do so.', - 'csv_more_information_text' => 'If the import fails, you can use this configuration file so you don\'t have to start all over again. But, if the import succeeds, it will be easier to upload similar CSV files.', + 'csv_download_config_text' => 'Tudo o que você configurou pode ser baixado como um arquivo de configuração. Clique no botão para fazê-lo.', + 'csv_more_information_text' => 'Se a importação falhar, você pode usar este arquivo de configuração, então você não tem que começar tudo de novo. Mas, se a importação for bem-sucedido, será mais fácil carregar arquivos CSV semelhantes.', 'csv_do_download_config' => 'Download do arquivo de configuração.', 'csv_empty_description' => '(descrição vazia)', 'csv_upload_form' => 'Formulário de Upload do CSV', 'csv_index_unsupported_warning' => 'O importador de CSV está incapaz de fazer o seguinte:', - 'csv_unsupported_map' => 'The importer cannot map the column ":columnRole" to existing values in the database.', - 'csv_unsupported_value' => 'The importer does not know how to handle values in columns marked as ":columnRole".', - 'csv_cannot_store_value' => 'The importer has not reserved space for columns marked ":columnRole" and will be incapable of processing them.', + 'csv_unsupported_map' => 'O importador não pode mapear a coluna ":columnRole" para os valores existentes no banco de dados.', + 'csv_unsupported_value' => 'O importador não sabe como lidar com valores em colunas marcadas como ":columnRole".', + 'csv_cannot_store_value' => 'O importador não reservou espaço para colunas marcadas ":columnRole" e será incapaz de processá-los.', 'csv_process_title' => 'Importação do CSV terminou!', 'csv_process_text' => 'O importador do CSV terminou e processou :rows linhas', 'csv_row' => 'Linha', @@ -393,24 +395,24 @@ return [ 'csv_column_opposing-iban' => 'Opondo-se conta (IBAN)', 'csv_column_opposing-id' => 'ID da conta opostas (Firefly equivalente)', 'csv_column_opposing-name' => 'Opondo-se conta (nome)', - 'csv_column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'csv_column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'csv_column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'csv_column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'csv_column_sepa-db' => 'SEPA Direct Debet', + 'csv_column_rabo-debet-credit' => 'Indicador de débito/crédito do Rabobank', + 'csv_column_ing-debet-credit' => 'Indicador de débito/crédito do ING', + 'csv_column_sepa-ct-id' => 'Transferência de crédito SEPA fim-a-fim ID', + 'csv_column_sepa-ct-op' => 'Transferência de crédito SEPA conta contrária', + 'csv_column_sepa-db' => 'SEPA Débito Direto', 'csv_column_tags-comma' => 'Tags (separadas por vírgula)', 'csv_column_tags-space' => 'Tags (separadas por espaço)', 'csv_column_account-number' => 'Conta de ativo (número da conta)', - 'csv_column_opposing-number' => 'Opposing account (account number)', - 'csv_specifix_RabobankDescription' => 'Select this when you\'re importing Rabobank CSV export files.', - 'csv_specifix_AbnAmroDescription' => 'Select this when you\'re importing ABN AMRO CSV export files.', - 'csv_specifix_Dummy' => 'Checking this has no effect whatsoever.', - 'csv_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'csv_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'csv_date_parse_error' => 'Could not parse a valid date from ":value", using the format ":format". Are you sure your CSV is correct?', - 'could_not_recover' => 'Could not continue from the previous step. Your progress has been lost :(. The log files will tell you what happened.', - 'must_select_roles' => 'You must select some roles for your file content, or the process cannot continue.', - 'invalid_mapping' => 'You have submitted an invalid mapping. The process cannot continue.', + 'csv_column_opposing-number' => 'Conta Contrária (número da conta)', + 'csv_specifix_RabobankDescription' => 'Selecione esta opção quando você estiver importando arquivos de exportação de CSV do Rabobank.', + 'csv_specifix_AbnAmroDescription' => 'Selecione esta opção quando você estiver importando arquivos de exportação de CSV do ABN AMRO.', + 'csv_specifix_Dummy' => 'Marcar esta não tem qualquer efeito.', + 'csv_import_account_help' => 'Se seu arquivo CSV não contém informações sobre sua(s) conta(s) ativa(s), use este combobox para selecionar para qual conta pertencem as transações em CSV.', + 'csv_delimiter_help' => 'Escolha o delimitador de campo que é usado em seu arquivo de entrada. Se não estiver claro, a vírgula é a opção mais segura.', + 'csv_date_parse_error' => 'Não foi possível analisar uma data válida de ":value", usando o formato ":formato". Tem certeza que seu CSV está correto?', + 'could_not_recover' => 'Não poderia continuar da etapa anterior. Seu progresso foi perdido :(. Os arquivos de log você dirão o que aconteceu.', + 'must_select_roles' => 'Você deve selecionar algumas funções para o conteúdo do arquivo, ou o processo não pode continuar.', + 'invalid_mapping' => 'Você se submeteu a um mapeamento inválido. O processo não pode continuar.', 'no_file_uploaded' => 'Parece que você não enviou um arquivo.', @@ -442,11 +444,11 @@ return [ // new user: 'submit' => 'Enviar', 'getting_started' => 'Iniciar', - 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', - 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', - 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', - 'stored_new_account_new_user' => 'Yay! Your new account has been stored.', - 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', + 'to_get_started' => 'Para começar com o Firefly, por favor digite o nome do seu banco atual e o saldo de sua conta corrente:', + 'savings_balance_text' => 'Se você tem uma conta poupança, por favor, digite o saldo atual de sua conta de poupança:', + 'cc_balance_text' => 'Se você tiver um cartão de crédito, por favor digite o limite do seu cartão de crédito.', + 'stored_new_account_new_user' => 'Yay! Sua nova conta foi armazenada.', + 'stored_new_accounts_new_user' => 'Yay! Suas novas contas foram armazenadas.', // forms: 'mandatoryFields' => 'Campos obrigatórios', @@ -459,8 +461,11 @@ return [ 'store_new_budget' => 'Armazenar novo orçamento', 'stored_new_budget' => 'Novo orçamento armazenado ":name"', 'availableIn' => 'Disponível em :date', + 'available_between' => 'Available between :start and :end', 'transactionsWithoutBudget' => 'Despesas sem orçamentos', 'transactionsWithoutBudgetDate' => 'Despesas sem orçamentos em :date', + 'transactions_no_budget' => 'Expenses without budget between :start and :end', + 'spent_between' => 'Spent between :start and :end', 'createBudget' => 'Novo orçamento', 'inactiveBudgets' => 'Orçamentos inativos', 'without_budget_between' => 'Transações sem um orçamento entre :start e :end', @@ -471,20 +476,21 @@ return [ 'updated_budget' => 'Orçamento atualizado ":name"', 'update_amount' => 'Atualizar quantia', 'update_budget' => 'Atualizar Orçamento', + 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', // bills: - 'matching_on' => 'Matching on', - 'between_amounts' => 'between :low and :high.', - 'repeats' => 'Repeats', - 'connected_journals' => 'Connected transactions', - 'auto_match_on' => 'Automatically matched by Firefly', - 'auto_match_off' => 'Not automatically matched by Firefly', - 'next_expected_match' => 'Next expected match', + 'matching_on' => 'Corresponde em', + 'between_amounts' => 'entre :low e :high.', + 'repeats' => 'Repetições', + 'connected_journals' => 'Transações conectadas', + 'auto_match_on' => 'Coincidido automaticamente pelo Firefly', + 'auto_match_off' => 'Não coincidido automaticamente pelo Firefly', + 'next_expected_match' => 'Próximo correspondente esperado', 'delete_bill' => 'Apagar fatura ":name"', 'deleted_bill' => 'Fatura apagada ":name"', 'edit_bill' => 'Editar fatura ":name"', - 'more' => 'More', - 'rescan_old' => 'Rescan old transactions', + 'more' => 'Mais', + 'rescan_old' => 'Examinar novamente o transações antigas', 'update_bill' => 'Atualizar fatura', 'updated_bill' => 'Fatura atualizada ":name"', 'store_new_bill' => 'Armazenar nova fatura', @@ -528,7 +534,7 @@ return [ 'save_transactions_by_moving' => 'Salve essas transações, movendo-os para outra conta:', 'stored_new_account' => 'Nova conta ":name" armazenado!', 'updated_account' => 'Conta ":name" atualizada', - 'credit_card_options' => 'Credit card options', + 'credit_card_options' => 'Opções de cartão de crédito', // categories: 'new_category' => 'Nova categoria', @@ -560,16 +566,16 @@ return [ 'deleted_deposit' => 'Depósito ":description" excluído com sucesso', 'deleted_transfer' => 'Transferência ":description" excluída com sucesso', 'stored_journal' => 'Transação ":description" excluída com sucesso', - 'select_transactions' => 'Select transactions', - 'stop_selection' => 'Stop selecting transactions', - 'edit_selected' => 'Edit selected', - 'delete_selected' => 'Delete selected', - 'mass_delete_journals' => 'Delete a number of transactions', - 'mass_edit_journals' => 'Edit a number of transactions', - 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', - 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', - 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', - 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', + 'select_transactions' => 'Selecione as transações', + 'stop_selection' => 'Parar de selecionar transações', + 'edit_selected' => 'Editar selecionado', + 'delete_selected' => 'Excluir selecionados', + 'mass_delete_journals' => 'Excluir um número de transacções', + 'mass_edit_journals' => 'Editar um número de transacções', + 'cannot_edit_other_fields' => 'Você não pode editar em massa outros campos que não esse aqui, porque não há espaço para mostrá-los. Por favor siga o link e editá-los por um por um, se você precisar editar esses campos.', + 'perm-delete-many' => 'Exclusão de muitos itens de uma só vez pode ser muito perturbador. Por favor, seja cauteloso.', + 'mass_deleted_transactions_success' => 'Excluído :amount de transação(ões).', + 'mass_edited_transactions_success' => 'Atualizado :amount de transação(ões)', // new user: @@ -666,7 +672,7 @@ return [ 'splitByAccount' => 'Dividir por conta', 'balancedByTransfersAndTags' => 'Balanço por transferências e tags', 'coveredWithTags' => 'Coberto com tags', - 'leftUnbalanced' => 'Left unbalanced', + 'leftUnbalanced' => 'Deixar desequilibrado', 'expectedBalance' => 'Saldo Experado', 'outsideOfBudgets' => 'Fora do orçamento', 'leftInBudget' => 'Deixou no orçamento', @@ -674,7 +680,7 @@ return [ 'noCategory' => '(sem categoria)', 'notCharged' => 'Não cobrado (ainda)', 'inactive' => 'Inativo', - 'active' => 'Active', + 'active' => 'Ativo', 'difference' => 'Diferente', 'in' => 'Entrada', 'out' => 'Saída', @@ -690,8 +696,8 @@ return [ 'report_type' => 'Tipo de relatório', 'report_type_default' => 'Relatório financeiro padrão', 'report_type_audit' => 'Visão geral do histórico de transação (auditoria)', - 'report_type_meta-history' => 'Categories, budgets and bills overview', - 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', + 'report_type_meta-history' => 'Visão geral de categorias, orçamentos e faturas', + 'more_info_help' => 'Mais informações sobre esses tipos de relatórios podem ser encontradas nas páginas de ajuda. Pressione o ícone (?) no canto superior direito.', 'report_included_accounts' => 'Contas incluídas', 'report_date_range' => 'Período', 'report_include_help' => 'Em todos os casos, as transferências para contas compartilhadas contam como despesas e transferências de contas compartilhadas contam como renda.', @@ -707,7 +713,7 @@ return [ 'audit_end_balance' => 'Saldo da conta : account_name no final de :end foi :balance', // charts: - 'chart' => 'Chart', + 'chart' => 'Gráfico', 'dayOfMonth' => 'Dia do mês', 'month' => 'Mês', 'budget' => 'Orçamento', @@ -782,10 +788,10 @@ return [ 'no_year' => 'Nenhum ano definido', 'no_month' => 'Nenhum mês definido', 'tag_title_nothing' => 'Tags padrões', - 'tag_title_balancingAct' => 'Balancing act tags', - 'tag_title_advancePayment' => 'Advance payment tags', - 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', - 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', + 'tag_title_balancingAct' => 'Saldo das tags', + 'tag_title_advancePayment' => 'Tags de pagamento do adiantamento', + 'tags_introduction' => 'Geralmente tags são palavras singulares, projetadas para rapidamente agrupar itens usando coisas como caro, conta ou para a festa. Em Firefly III, tags podem ter propriedades mais como uma data, descrição e localização. Isso permite que você una as operações de uma forma mais significativa. Por exemplo, você poderia fazer uma tag chamada ceia de Natal com amigos e adicionar informações sobre o restaurante. Tais tags são "singulares", só use-as para uma ocasião única, talvez com várias transações.', + 'tags_group' => 'Tags agrupam transações, que torna possível armazenar os reembolsos (no caso você empreste dinheiro para os outros) e outros "balancetes" onde as despesas são somadas (os pagamentos na sua TV nova) ou onde as despesas e depósitos estão anulando uns aos outros (compra algo com dinheiro guardado). Isso é tudo para você. Usando tags à moda antiga claro é sempre possível.', 'tags_start' => 'Crie uma tag para começar ou insira tags ao criar novas transações.', diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index a372d46f85..b9c2081e3e 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -112,12 +112,12 @@ return [ 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?', 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?', 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', + 'mass_journal_are_you_sure' => 'Tem a certeza que pretende apagar estas transações?', 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', 'permDeleteWarning' => 'Exclusão de dados do Firely são permanentes e não podem ser desfeitos.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', + 'mass_make_selection' => 'Você ainda pode evitar que itens sejam excluídos, removendo a caixa de seleção.', + 'delete_all_permanently' => 'Exclua os selecionados permanentemente', + 'update_all_journals' => 'Atualizar essas transações', 'also_delete_transactions' => 'A única transação ligada a essa conta será excluída também.|Todas as :count transações ligadas a esta conta serão excluídas também.', 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluída também.|Todos as :count regras ligadas a este grupo de regras serão excluídas também.', 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluído também.|Todos os :count cofrinhos conectados a esta conta serão excluídos também.', diff --git a/resources/lang/pt_BR/help.php b/resources/lang/pt_BR/help.php index ed5f833cb9..9f720e032e 100644 --- a/resources/lang/pt_BR/help.php +++ b/resources/lang/pt_BR/help.php @@ -11,80 +11,80 @@ return [ // tour! 'main-content-title' => 'Bem Vindo ao Firefly III', - 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', - 'sidebar-toggle-title' => 'Sidebar to create stuff', - 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', - 'account-menu-title' => 'All your accounts', - 'account-menu-text' => 'Here you can find all the accounts you\'ve made.', + 'main-content-text' => 'Faça um favor a você mesmo e siga este pequeno guia para certificar-se de que sabe o caminho de volta.', + 'sidebar-toggle-title' => 'Barra lateral para criar coisas', + 'sidebar-toggle-text' => 'Escondido sob o ícone de adição são todos os botões para criar coisas novas. Contas, transações, tudo!', + 'account-menu-title' => 'Todas suas contas', + 'account-menu-text' => 'Aqui você pode encontrar todas as contas que você fez.', 'budget-menu-title' => 'Orçamentos', - 'budget-menu-text' => 'Use this page to organise your finances and limit spending.', + 'budget-menu-text' => 'Use esta página para organizar suas finanças e limitar as despesas.', 'report-menu-title' => 'Relatórios', - 'report-menu-text' => 'Check this out when you want a solid overview of your finances.', + 'report-menu-text' => 'Retire isso quando você quiser uma visão geral de suas finanças.', 'transaction-menu-title' => 'Transações', - 'transaction-menu-text' => 'All transactions you\'ve created can be found here.', + 'transaction-menu-text' => 'Todas as transações que você criou podem ser encontradas aqui.', 'option-menu-title' => 'Opções', - 'option-menu-text' => 'This is pretty self-explanatory.', + 'option-menu-text' => 'Isto é bastante auto-explicativo.', 'main-content-end-title' => 'Fim!', - 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.', + 'main-content-end-text' => 'Lembre-se que cada página tem um pequeno ponto de interrogação na parte superior direita. Clique nele para obter ajuda sobre a página que você está.', 'index' => 'índice', 'home' => 'casa', - 'accounts-index' => 'accounts.index', - 'accounts-create' => 'accounts.create', - 'accounts-edit' => 'accounts.edit', - 'accounts-delete' => 'accounts.delete', - 'accounts-show' => 'accounts.show', - 'attachments-edit' => 'attachments.edit', - 'attachments-delete' => 'attachments.delete', - 'attachments-show' => 'attachments.show', - 'attachments-preview' => 'attachments.preview', - 'bills-index' => 'bills.index', - 'bills-create' => 'bills.create', - 'bills-edit' => 'bills.edit', - 'bills-delete' => 'bills.delete', - 'bills-show' => 'bills.show', - 'budgets-index' => 'budgets.index', - 'budgets-create' => 'budgets.create', - 'budgets-edit' => 'budgets.edit', - 'budgets-delete' => 'budgets.delete', - 'budgets-show' => 'budgets.show', - 'budgets-noBudget' => 'budgets.noBudget', - 'categories-index' => 'categories.index', - 'categories-create' => 'categories.create', - 'categories-edit' => 'categories.edit', - 'categories-delete' => 'categories.delete', - 'categories-show' => 'categories.show', - 'categories-show-date' => 'categories.show.date', - 'categories-noCategory' => 'categories.noCategory', + 'accounts-index' => 'Contas', + 'accounts-create' => 'Criando Contas', + 'accounts-edit' => 'Editando Contas', + 'accounts-delete' => 'Apagando Contas', + 'accounts-show' => 'Detalhes de Contas', + 'attachments-edit' => 'Editando Anexos', + 'attachments-delete' => 'Apagando Anexos', + 'attachments-show' => 'Detalhes de Anexos', + 'attachments-preview' => 'Previsualisação de Anexos', + 'bills-index' => 'Faturas', + 'bills-create' => 'Criando Faturas', + 'bills-edit' => 'Editando Faturas', + 'bills-delete' => 'Apagando Faturas', + 'bills-show' => 'Detalhes da Fatura', + 'budgets-index' => 'Orçamentos', + 'budgets-create' => 'Criando Orçamentos', + 'budgets-edit' => 'Editando Orçamentos', + 'budgets-delete' => 'Apagando Orçamentos', + 'budgets-show' => 'Detalhes de Orçamentos', + 'budgets-noBudget' => 'Sem Orçamentos', + 'categories-index' => 'Categorias', + 'categories-create' => 'Criando Categorias', + 'categories-edit' => 'Editando Categorias', + 'categories-delete' => 'Apagando Categorias', + 'categories-show' => 'Detalhes de Categorias', + 'categories-show-date' => 'Detalhes da Categoria por Data', + 'categories-noCategory' => 'Sem Categorias', 'csv-index' => 'Carregar e importar um arquivo CSV', - 'csv-column-roles' => 'csv.column-roles', - 'csv-map' => 'csv.map', - 'csv-download-config-page' => 'csv.download-config-page', - 'csv-process' => 'csv.process', - 'currency-index' => 'currency.index', - 'currency-create' => 'currency.create', - 'currency-edit' => 'currency.edit', - 'currency-delete' => 'currency.delete', - 'new-user-index' => 'new-user.index', - 'piggy-banks-index' => 'piggy-banks.index', - 'piggy-banks-create' => 'piggy-banks.create', - 'piggy-banks-edit' => 'piggy-banks.edit', - 'piggy-banks-delete' => 'piggy-banks.delete', - 'piggy-banks-show' => 'piggy-banks.show', - 'preferences' => 'preferences', - 'profile' => 'profile', - 'profile-change-password' => 'profile.change-password', - 'profile-delete-account' => 'profile.delete-account', - 'reports-index' => 'reports.index', - 'reports-report' => 'reports.report', - 'search' => 'search', - 'tags-index' => 'tags.index', - 'tags-create' => 'tags.create', - 'tags-show' => 'tags.show', - 'tags-edit' => 'tags.edit', - 'tags-delete' => 'tags.delete', - 'transactions-index' => 'transactions.index', - 'transactions-create' => 'transactions.create', - 'transactions-edit' => 'transactions.edit', - 'transactions-delete' => 'transactions.delete', - 'transactions-show' => 'transactions.show', + 'csv-column-roles' => 'CSV Papel da coluna', + 'csv-map' => 'Mapeamento CSV', + 'csv-download-config-page' => 'Baixando CSV de Configuração', + 'csv-process' => 'Processando CSV', + 'currency-index' => 'Moedas', + 'currency-create' => 'Criando Moedas', + 'currency-edit' => 'Editando Moedas', + 'currency-delete' => 'Apagando Moedas', + 'new-user-index' => 'Novo Usuário', + 'piggy-banks-index' => 'Cofrinhos', + 'piggy-banks-create' => 'Criando Cofrinhos', + 'piggy-banks-edit' => 'Editando Cofrinhos', + 'piggy-banks-delete' => 'Apagando Cofrinhos', + 'piggy-banks-show' => 'Detalhes do Cofrinho', + 'preferences' => 'preferências', + 'profile' => 'perfil', + 'profile-change-password' => 'Alterando Senha', + 'profile-delete-account' => 'Apagando Perfil', + 'reports-index' => 'Relatórios', + 'reports-report' => 'Relatório', + 'search' => 'pesquisa', + 'tags-index' => 'Tags', + 'tags-create' => 'Criando Tags', + 'tags-show' => 'Detalhes da Tag', + 'tags-edit' => 'Editando Tags', + 'tags-delete' => 'Apagando Tags', + 'transactions-index' => 'Transações', + 'transactions-create' => 'Criando Transações', + 'transactions-edit' => 'Editando Transações', + 'transactions-delete' => 'Apagando Transações', + 'transactions-show' => 'Detalhes da Transação', ]; From baff9780de5613b56b5d527f4421f9659f8fc80a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 11:05:52 +0200 Subject: [PATCH 047/206] Smaller report. [skip ci] --- app/Helpers/Collection/Balance.php | 8 +++++ app/Helpers/Report/BalanceReportHelper.php | 36 +++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/Helpers/Collection/Balance.php b/app/Helpers/Collection/Balance.php index cb3b4fa3c8..06edffe85e 100644 --- a/app/Helpers/Collection/Balance.php +++ b/app/Helpers/Collection/Balance.php @@ -59,5 +59,13 @@ class Balance return $this->balanceLines; } + /** + * @param Collection $balanceLines + */ + public function setBalanceLines(Collection $balanceLines) + { + $this->balanceLines = $balanceLines; + } + } diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index fbcb9e6930..5c9f08a90a 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -28,7 +28,6 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; - /** * Class BalanceReportHelper * @@ -85,6 +84,9 @@ class BalanceReportHelper implements BalanceReportHelperInterface $balance->addBalanceLine($this->createDifferenceBalanceLine($accounts, $spentData, $start, $end)); $balance->setBalanceHeader($header); + // remove budgets without expenses from balance lines: + $balance = $this->removeUnusedBudgets($balance); + return $balance; } @@ -305,4 +307,36 @@ class BalanceReportHelper implements BalanceReportHelperInterface return $tags; } + /** + * @param Balance $balance + * + * @return Balance + */ + private function removeUnusedBudgets(Balance $balance): Balance + { + $set = $balance->getBalanceLines(); + $newSet = new Collection; + /** @var BalanceLine $entry */ + foreach ($set as $entry) { + if (!is_null($entry->getBudget()->id)) { + $sum = '0'; + /** @var BalanceEntry $balanceEntry */ + foreach ($entry->getBalanceEntries() as $balanceEntry) { + $sum = bcadd($sum, $balanceEntry->getSpent()); + } + if (bccomp($sum, '0') === -1) { + $newSet->push($entry); + } + continue; + } + $newSet->push($entry); + } + + + $balance->setBalanceLines($newSet); + + return $balance; + + } + } From 8bcc319b7dd7feb1b14c31561d544a083491e448 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 11:34:48 +0200 Subject: [PATCH 048/206] Better demo warning and budget indication. --- app/Http/Controllers/Auth/AuthController.php | 5 ++--- resources/views/auth/register.twig | 4 ++-- resources/views/transactions/create.twig | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index fe39b4aedf..fa0307685c 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -15,7 +15,6 @@ use Illuminate\Mail\Message; use Illuminate\Support\Facades\Lang; use Log; use Mail; -use Request as Rq; use Session; use Swift_TransportException; use Validator; @@ -143,9 +142,9 @@ class AuthController extends Controller */ public function showRegistrationForm() { - $host = Rq::getHttpHost(); + $showDemoWarning = env('SHOW_DEMO_WARNING', false); - return view('auth.register', compact('host')); + return view('auth.register', compact('showDemoWarning')); } /** diff --git a/resources/views/auth/register.twig b/resources/views/auth/register.twig index 4d7ab7394a..eafefc2609 100644 --- a/resources/views/auth/register.twig +++ b/resources/views/auth/register.twig @@ -17,7 +17,7 @@
- {% if host == 'geld.nder.be' or host == 'firefly.app' %} + {% if showDemoWarning %} {% endif %} @@ -27,7 +27,7 @@
- {% if host == 'geld.nder.be' or host == 'firefly.app' %} + {% if showDemoWarning %}

You will receive an email from Firefly III. If your email address is incorrect, your account may not work.

{% endif %} diff --git a/resources/views/transactions/create.twig b/resources/views/transactions/create.twig index 8d10ff20ae..f263b38ff2 100644 --- a/resources/views/transactions/create.twig +++ b/resources/views/transactions/create.twig @@ -63,7 +63,7 @@
- {% if budgets|length > 0 %} + {% if budgets|length > 1 %} {{ ExpandedForm.select('budget_id',budgets,0) }} {% else %} {{ ExpandedForm.select('budget_id',budgets,0, {helpText: trans('firefly.no_budget_pointer')}) }} From 568186828c86106cda35e17cf532d074329f85c0 Mon Sep 17 00:00:00 2001 From: Antonio Spinelli Date: Thu, 28 Apr 2016 14:40:59 -0300 Subject: [PATCH 049/206] add brazilian currency and translation fix #257 --- config/firefly.php | 2 +- database/seeds/TransactionCurrencySeeder.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/firefly.php b/config/firefly.php index 076e3396b7..393aa77c39 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -111,7 +111,7 @@ return [ 'languages' => [ 'en_US' => ['name_locale' => 'English', 'name_english' => 'English', 'complete' => true], 'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch', 'complete' => true], - 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portugese (Brazil)', 'complete' => false], + 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)', 'complete' => true], 'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French', 'complete' => false], ], 'lang' => [ diff --git a/database/seeds/TransactionCurrencySeeder.php b/database/seeds/TransactionCurrencySeeder.php index 7b7486ce12..9260312c18 100644 --- a/database/seeds/TransactionCurrencySeeder.php +++ b/database/seeds/TransactionCurrencySeeder.php @@ -15,6 +15,7 @@ class TransactionCurrencySeeder extends Seeder TransactionCurrency::create(['code' => 'EUR', 'name' => 'Euro', 'symbol' => '€']); TransactionCurrency::create(['code' => 'USD', 'name' => 'US Dollar', 'symbol' => '$']); TransactionCurrency::create(['code' => 'HUF', 'name' => 'Hungarian forint', 'symbol' => 'Ft']); + TransactionCurrency::create(['code' => 'BRL', 'name' => 'Real', 'symbol' => 'R$']); } } From f266b92ef12af719af929c9a6cb9b24991d21158 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 17:26:38 +0200 Subject: [PATCH 050/206] This catches issue #247 --- app/Console/Commands/VerifyDatabase.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 3238c2c0dd..3b21b24cb3 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -72,6 +72,9 @@ class VerifyDatabase extends Command $this->reportTransactions(); // deleted accounts that still have not deleted transactions or journals attached to them. $this->reportDeletedAccounts(); + + // report on journals with no transactions at all. + $this->reportNoTransactions(); } /** @@ -210,6 +213,28 @@ class VerifyDatabase extends Command } } + private function reportNoTransactions() + { + /* + * select transaction_journals.id, count(transactions.id) as transaction_count from transaction_journals +left join transactions ON transaction_journals.id = transactions.transaction_journal_id +group by transaction_journals.id +having transaction_count = 0 + */ + $set = TransactionJournal + ::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->groupBy('transaction_journals.id') + ->having('transaction_count', '=', 0) + ->get(['transaction_journals.id', DB::raw('COUNT(`transactions`.`id`) as `transaction_count`')]); + + foreach ($set as $entry) { + $this->error( + 'Error: Journal #' . $entry->id . ' has zero transactions. Open table `transaction_journals` and delete the entry with id #' . $entry->id + ); + } + + } + /** * Reports for each user when the sum of their transactions is not zero. */ From 0ef3d0cf03bd0537b7b098beac98caa6820ae78c Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 17:26:59 +0200 Subject: [PATCH 051/206] Better message for issue #247 --- app/Support/Models/TransactionJournalSupport.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index 253361547a..8786338f0a 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -12,12 +12,14 @@ namespace FireflyIII\Support\Models; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\CacheProperties; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Log; /** * Class TransactionJournalSupport @@ -43,7 +45,11 @@ class TransactionJournalSupport extends Model } $transaction = $journal->transactions->sortByDesc('amount')->first(); - $amount = $transaction->amount; + if (is_null($transaction)) { + Log::error('Transaction journal #' . $journal->id . ' has ZERO transactions (or they have been deleted).'); + throw new FireflyException('Transaction journal #' . $journal->id . ' has ZERO transactions. Visit this page for a solution: https://git.io/vwPFY'); + } + $amount = $transaction->amount; if ($journal->isWithdrawal()) { $amount = bcmul($amount, '-1'); } From 4af8272faa9fba8476ee4670f2a26077873ff516 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 17:29:13 +0200 Subject: [PATCH 052/206] Updates to transactions. --- .env.example | 1 + .../Transaction/SplitController.php | 64 +++++++ .../Controllers/TransactionController.php | 39 ++-- app/Http/Requests/JournalFormRequest.php | 26 ++- app/Http/routes.php | 5 + .../Journal/JournalRepository.php | 67 ++++--- app/Support/ExpandedForm.php | 6 +- public/js/ff/transactions/create-edit.js | 43 ++++- public/js/ff/transactions/create.js | 59 ++++--- resources/lang/en_US/form.php | 2 + resources/views/form/options.twig | 15 ++ resources/views/form/static.twig | 2 +- .../views/split/journals/from-store.twig | 166 ++++++++++++++++++ resources/views/transactions/create.twig | 19 +- 14 files changed, 416 insertions(+), 98 deletions(-) create mode 100644 app/Http/Controllers/Transaction/SplitController.php create mode 100644 resources/views/split/journals/from-store.twig diff --git a/.env.example b/.env.example index 20f71dc696..dcca3ab865 100755 --- a/.env.example +++ b/.env.example @@ -36,6 +36,7 @@ SEND_REGISTRATION_MAIL=true MUST_CONFIRM_ACCOUNT=false SHOW_INCOMPLETE_TRANSLATIONS=false +SHOW_DEMO_WARNING=false ANALYTICS_ID= RUNCLEANUP=true diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php new file mode 100644 index 0000000000..ba13da0ce4 --- /dev/null +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -0,0 +1,64 @@ +find(intval($journalData['amount_currency_id_amount'])); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + if (!is_array($journalData)) { + throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. + } + // echo '
';
+        //        var_dump($journalData);
+        //        echo '
'; + // exit; + + return view('split.journals.from-store', compact('currency', 'assetAccounts', 'budgets'))->with('data', $journalData); + + + } + + public function postJournalFromStore() + { + // forget temp journal data + // Session::forget('temporary_split_data'); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 6e615cca70..1b251f9b10 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -54,28 +54,26 @@ class TransactionController extends Controller */ public function create(ARI $repository, string $what = TransactionType::DEPOSIT) { - $what = strtolower($what); - $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); - $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); - $uploadSize = min($maxFileSize, $maxPostSize); - $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); - $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); - $budgets[0] = trans('firefly.no_budget'); - $piggyBanks = Auth::user()->piggyBanks()->orderBy('order', 'ASC')->get(); + /** @var BudgetRepositoryInterface $budgetRepository */ + $budgetRepository = app(BudgetRepositoryInterface::class); + + /** @var PiggyBankRepositoryInterface $piggyRepository */ + $piggyRepository = app(PiggyBankRepositoryInterface::class); + + $what = strtolower($what); + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $assetAccounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $piggyBanks = $piggyRepository->getPiggyBanks(); /** @var PiggyBank $piggy */ foreach ($piggyBanks as $piggy) { $piggy->name = $piggy->name . ' (' . Amount::format($piggy->currentRelevantRep()->currentamount, false) . ')'; } - $piggies = ExpandedForm::makeSelectList($piggyBanks); - $piggies[0] = trans('form.noPiggybank'); - $preFilled = Session::has('preFilled') ? session('preFilled') : []; - $respondTo = ['account_id', 'account_from_id']; - $subTitle = trans('form.add_new_' . $what); + $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); + $preFilled = Session::has('preFilled') ? session('preFilled') : []; + $subTitle = trans('form.add_new_' . $what); - foreach ($respondTo as $r) { - $preFilled[$r] = Input::get($r); - } Session::put('preFilled', $preFilled); // put previous url in session if not redirect from store (not "create another"). @@ -89,7 +87,7 @@ class TransactionController extends Controller asort($piggies); - return view('transactions.create', compact('accounts', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle')); + return view('transactions.create', compact('assetAccounts', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle')); } /** @@ -430,7 +428,14 @@ class TransactionController extends Controller */ public function store(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att) { + $doSplit = intval($request->get('split_journal')) === 1; $journalData = $request->getJournalData(); + if ($doSplit) { + // put all journal data in the session and redirect to split routine. + Session::put('temporary_split_data', $journalData); + + return redirect(route('split.journal.from-store')); + } // if not withdrawal, unset budgetid. if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) { diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index a40760a54d..5b5a7905d7 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -37,11 +37,10 @@ class JournalFormRequest extends Request return [ 'what' => $this->get('what'), 'description' => $this->get('description'), - 'account_id' => intval($this->get('account_id')), - 'account_from_id' => intval($this->get('account_from_id')), - 'account_to_id' => intval($this->get('account_to_id')), - 'expense_account' => $this->get('expense_account') ?? '', - 'revenue_account' => $this->get('revenue_account') ?? '', + 'source_account_id' => intval($this->get('source_account_id')), + 'source_account_name' => $this->get('source_account_name') ?? '', + 'account_destination_id' => intval($this->get('account_destination_id')), + 'destination_account_name' => $this->get('destination_account_name') ?? '', 'amount' => round($this->get('amount'), 2), 'user' => Auth::user()->id, 'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')), @@ -70,28 +69,27 @@ class JournalFormRequest extends Request 'process_date' => 'date', 'book_date' => 'date', 'interest_date' => 'date', + 'category' => 'between:1,255', 'amount_currency_id_amount' => 'required|exists:transaction_currencies,id', ]; switch ($what) { case strtolower(TransactionType::WITHDRAWAL): - $rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - $rules['expense_account'] = 'between:1,255'; - $rules['category'] = 'between:1,255'; + $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['destination_account_name'] = 'between:1,255'; if (intval(Input::get('budget_id')) != 0) { $rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets'; } break; case strtolower(TransactionType::DEPOSIT): - $rules['category'] = 'between:1,255'; - $rules['account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - $rules['revenue_account'] = 'between:1,255'; + $rules['source_account_name'] = 'between:1,255'; + $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; break; case strtolower(TransactionType::TRANSFER): - $rules['account_from_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_to_id'; - $rules['account_to_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:account_from_id'; - $rules['category'] = 'between:1,255'; + $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id'; + $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id'; + break; default: throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.'); diff --git a/app/Http/routes.php b/app/Http/routes.php index 0c4ce4b360..3a01ba5daf 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -342,6 +342,11 @@ Route::group( */ Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']); + /** + * Split controller + */ + Route::get('/transaction/split', ['uses' => 'Transaction\SplitController@journalFromStore', 'as' => 'split.journal.from-store']); + /** * Tag Controller */ diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index fa86ab461e..128996bd9b 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -248,19 +248,19 @@ class JournalRepository implements JournalRepositoryInterface } // store accounts (depends on type) - list($fromAccount, $toAccount) = $this->storeAccounts($transactionType, $data); + list($sourceAccount, $destinationAccount) = $this->storeAccounts($transactionType, $data); // store accompanying transactions. Transaction::create( // first transaction. [ - 'account_id' => $fromAccount->id, + 'account_id' => $sourceAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $data['amount'] * -1, ] ); Transaction::create( // second transaction. [ - 'account_id' => $toAccount->id, + 'account_id' => $destinationAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $data['amount'], ] @@ -391,38 +391,38 @@ class JournalRepository implements JournalRepositoryInterface */ protected function storeAccounts(TransactionType $type, array $data): array { - $fromAccount = null; - $toAccount = null; + $sourceAccount = null; + $destinationAccount = null; switch ($type->type) { case TransactionType::WITHDRAWAL: - list($fromAccount, $toAccount) = $this->storeWithdrawalAccounts($data); + list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data); break; case TransactionType::DEPOSIT: - list($fromAccount, $toAccount) = $this->storeDepositAccounts($data); + list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data); break; case TransactionType::TRANSFER: - $fromAccount = Account::find($data['account_from_id']); - $toAccount = Account::find($data['account_to_id']); + $sourceAccount = Account::find($data['account_from_id']); + $destinationAccount = Account::find($data['account_to_id']); break; default: throw new FireflyException('Did not recognise transaction type.'); } - if (is_null($toAccount)) { + if (is_null($destinationAccount)) { Log::error('"to"-account is null, so we cannot continue!', ['data' => $data]); throw new FireflyException('"to"-account is null, so we cannot continue!'); } - if (is_null($fromAccount)) { + if (is_null($sourceAccount)) { Log::error('"from"-account is null, so we cannot continue!', ['data' => $data]); throw new FireflyException('"from"-account is null, so we cannot continue!'); } - return [$fromAccount, $toAccount]; + return [$sourceAccount, $destinationAccount]; } /** @@ -432,21 +432,22 @@ class JournalRepository implements JournalRepositoryInterface */ protected function storeDepositAccounts(array $data): array { - $toAccount = Account::find($data['account_id']); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_destination_id'])->first(['accounts.*']); - if (strlen($data['revenue_account']) > 0) { + if (strlen($data['source_account_name']) > 0) { $fromType = AccountType::where('type', 'Revenue account')->first(); $fromAccount = Account::firstOrCreateEncrypted( - ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1] + ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1] ); + return [$fromAccount, $destinationAccount]; } else { - $toType = AccountType::where('type', 'Cash account')->first(); + $fromType = AccountType::where('type', 'Cash account')->first(); $fromAccount = Account::firstOrCreateEncrypted( - ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1] + ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1] ); } - return [$fromAccount, $toAccount]; + return [$fromAccount, $destinationAccount]; } /** @@ -456,20 +457,28 @@ class JournalRepository implements JournalRepositoryInterface */ protected function storeWithdrawalAccounts(array $data): array { - $fromAccount = Account::find($data['account_id']); + $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); - if (strlen($data['expense_account']) > 0) { - $toType = AccountType::where('type', 'Expense account')->first(); - $toAccount = Account::firstOrCreateEncrypted( - ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1] - ); - } else { - $toType = AccountType::where('type', 'Cash account')->first(); - $toAccount = Account::firstOrCreateEncrypted( - ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1] + if (strlen($data['destination_account_name']) > 0) { + $destinationType = AccountType::where('type', 'Expense account')->first(); + $destinationAccount = Account::firstOrCreateEncrypted( + [ + 'user_id' => $data['user'], + 'account_type_id' => $destinationType->id, + 'name' => $data['destination_account_name'], + 'active' => 1, + ] ); + + return [$sourceAccount, $destinationAccount]; } + $destinationType = AccountType::where('type', 'Cash account')->first(); + $destinationAccount = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1] + ); + + return [$sourceAccount, $destinationAccount]; + - return [$fromAccount, $toAccount]; } } diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 19981d749d..e56e13b466 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace FireflyIII\Support; use Amount as Amt; +use Carbon\Carbon; use Eloquent; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -220,7 +221,7 @@ class ExpandedForm } /** - * @param Collection $set + * @param \Illuminate\Support\Collection $set * * @return array */ @@ -443,6 +444,9 @@ class ExpandedForm } catch (RuntimeException $e) { // don't care about session errors. } + if ($value instanceof Carbon) { + $value = $value->format('Y-m-d'); + } return $value; diff --git a/public/js/ff/transactions/create-edit.js b/public/js/ff/transactions/create-edit.js index 8791d8a8fc..9bcf6b18f1 100644 --- a/public/js/ff/transactions/create-edit.js +++ b/public/js/ff/transactions/create-edit.js @@ -8,9 +8,18 @@ /* globals what:true, $, doSwitch, txt, middleCrumbName, title,button, middleCrumbUrl, piggiesLength, breadcrumbs */ $(document).ready(function () { "use strict"; - if ($('input[name="expense_account"]').length > 0) { + + // the destination account name is always an expense account name. + if ($('input[name="destination_account_name"]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="expense_account"]').typeahead({source: data}); + $('input[name="destination_account_name"]').typeahead({source: data}); + }); + } + + // also for multi input + if ($('input[name="destination_account_name[]"]').length > 0) { + $.getJSON('json/expense-accounts').done(function (data) { + $('input[name="destination_account_name[]"]').typeahead({source: data}); }); } @@ -27,9 +36,16 @@ $(document).ready(function () { }); } - if ($('input[name="revenue_account"]').length > 0) { + // the source account name is always a revenue account name. + if ($('input[name="source_account_name"]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="revenue_account"]').typeahead({source: data}); + $('input[name="source_account_name"]').typeahead({source: data}); + }); + } + // also for multi-input: + if ($('input[name="source_account_name[]"]').length > 0) { + $.getJSON('json/revenue-accounts').done(function (data) { + $('input[name="source_account_name[]"]').typeahead({source: data}); }); } @@ -38,10 +54,29 @@ $(document).ready(function () { $('input[name="description"]').typeahead({source: data}); }); } + // also for multi input: + if ($('input[name="description[]"]').length > 0 && what !== undefined) { + $.getJSON('json/transaction-journals/' + what).done(function (data) { + $('input[name="description[]"]').typeahead({source: data}); + }); + } + // and for the (rare) journal_description: + if ($('input[name="journal_description"]').length > 0 && what !== undefined) { + $.getJSON('json/transaction-journals/' + what).done(function (data) { + $('input[name="journal_description"]').typeahead({source: data}); + }); + } if ($('input[name="category"]').length > 0) { $.getJSON('json/categories').done(function (data) { $('input[name="category"]').typeahead({source: data}); }); } + + // also for multi input: + if ($('input[name="category[]"]').length > 0) { + $.getJSON('json/categories').done(function (data) { + $('input[name="category[]"]').typeahead({source: data}); + }); + } }); \ No newline at end of file diff --git a/public/js/ff/transactions/create.js b/public/js/ff/transactions/create.js index f446f19fd1..f3fc98a09c 100644 --- a/public/js/ff/transactions/create.js +++ b/public/js/ff/transactions/create.js @@ -33,43 +33,60 @@ function updateForm() { "use strict"; $('input[name="what"]').val(what); - switch (what) { case 'withdrawal': - $('#account_id_holder').show(); - $('#expense_account_holder').show(); - $('#revenue_account_holder').hide(); - $('#account_from_id_holder').hide(); - $('#account_to_id_holder').hide(); + // show source_id and dest_name: + $('#source_account_id_holder').show(); + $('#destination_account_name_holder').show(); + + // hide others: + $('#source_account_name_holder').hide(); + $('#destination_account_id_holder').hide(); + + // show budget: $('#budget_id_holder').show(); + + // hide piggy bank: $('#piggy_bank_id_holder').hide(); - - if ($('#ffInput_revenue_account').val().length > 0) { - $('#ffInput_expense_account').val($('#ffInput_revenue_account').val()); + // copy destination account name to + // source account name: + if ($('#ffInput_destination_account_name').val().length > 0) { + $('#ffInput_source_account_name').val($('#ffInput_destination_account_name').val()); } break; case 'deposit': - $('#account_id_holder').show(); - $('#expense_account_holder').hide(); - $('#revenue_account_holder').show(); - $('#account_from_id_holder').hide(); - $('#account_to_id_holder').hide(); + // show source_name and dest_id: + $('#source_account_name_holder').show(); + $('#destination_account_id_holder').show(); + + // hide others: + $('#source_account_id_holder').hide(); + $('#destination_account_name_holder').hide(); + + // hide budget $('#budget_id_holder').hide(); + + // hide piggy bank $('#piggy_bank_id_holder').hide(); - if ($('#ffInput_expense_account').val().length > 0) { - $('#ffInput_revenue_account').val($('#ffInput_expense_account').val()); + if ($('#ffInput_source_account_name').val().length > 0) { + $('#ffInput_destination_account_name').val($('#ffInput_source_account_name').val()); } break; case 'transfer': - $('#account_id_holder').hide(); - $('#expense_account_holder').hide(); - $('#revenue_account_holder').hide(); - $('#account_from_id_holder').show(); - $('#account_to_id_holder').show(); + // show source_id and dest_id: + $('#source_account_id_holder').show(); + $('#destination_account_id_holder').show(); + + // hide others: + $('#source_account_name_holder').hide(); + $('#destination_account_name_holder').hide(); + + + // hide budget $('#budget_id_holder').hide(); if (piggiesLength === 0) { $('#piggy_bank_id_holder').hide(); diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 4b18bfdec4..08eec339f7 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -24,6 +24,8 @@ return [ 'repeat_freq' => 'Repeats', 'account_from_id' => 'From account', 'account_to_id' => 'To account', + 'asset_destination_account' => 'Asset account (destination)', + 'asset_source_account' => 'Asset account (source)', 'account_id' => 'Asset account', 'budget_id' => 'Budget', 'openingBalance' => 'Opening balance', diff --git a/resources/views/form/options.twig b/resources/views/form/options.twig index 8a25895f34..4692b300b8 100644 --- a/resources/views/form/options.twig +++ b/resources/views/form/options.twig @@ -31,3 +31,18 @@
{% endif %} +{% if type == 'create' and name == 'transaction' %} +
+ + +
+
+
+
+
+{% endif %} \ No newline at end of file diff --git a/resources/views/form/static.twig b/resources/views/form/static.twig index e70ba496ba..480d6ab951 100644 --- a/resources/views/form/static.twig +++ b/resources/views/form/static.twig @@ -2,6 +2,6 @@
-

{{ value }}

+

{{ value|raw }}

diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig new file mode 100644 index 0000000000..363f2ebd6e --- /dev/null +++ b/resources/views/split/journals/from-store.twig @@ -0,0 +1,166 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists }} +{% endblock %} +{% block content %} + +
+
+
+
+

Split your new [type]

+ +
+ +
+
+
+

+ Firefly supports the "splitting" of an [expense / withdrawal / transfer]. + + It means that the amount of money you've spent/earned/transfered is divided between + several source accounts, destination accounts or budgets. + + Example for withdrawals: split your 20,- groceries so you pay 15,- from your "daily groceries" budget + and 5,- from your "cigarettes" budget. + + Example for deposits: split your 500,- salary so that 450,- is categorized "salary" and 50,- is categorized "reimbursed expenses". + + Example for transfers: split your 100,- transfer so that 50,- is put in the piggy bank "new couch" and 50,- is not. +

+
+
+
+
+
+
+
+
+

Transaction meta-data

+ +
+ +
+
+
+ {{ ExpandedForm.text('journal_description', data.description) }} + {{ ExpandedForm.staticText('amount', data.amount|formatAmount) }} + + {% if data.what == 'withdrawal' or data.what == 'transfer' %} + {{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.source_account_id]) }} + {% endif %} + + {% if data.what == 'deposit' or data.what == 'transfer' %} + {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }} + {% endif %} +
+
+
+
+
+
+

Transaction dates

+ +
+ +
+
+
+ {{ ExpandedForm.date('date', data.date) }} + + {{ ExpandedForm.date('interest_date', data.interest_date) }} + + {{ ExpandedForm.date('book_date', data.book_date) }} + + {{ ExpandedForm.date('process_date', data.process_date) }} +
+
+
+
+
+
+
+
+

Splits

+ +
+ +
+
+
+

+ Split your [x] in as many things as you want. By default the transaction will not split, there is just one entry. + Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you + do, Firefly will warn you but not correct you. +

+ + + + + + + {% if data.what == 'deposit' %} + + {% endif %} + + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'transfer' %} + + {% endif %} + + + + + + + {% if data.what == 'deposit' %} + + {% endif %} + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'withdrawal' %} + + {% endif %} + + + +
Split #DescriptionSourceDestinationAmountBudgetCategoryPiggy bank
#1{{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }} + {{ Form.input('text', 'source_account_name[]', data.source_account_name, {class: 'form-control'}) }} + + {{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }} + + {{ ExpandedForm.amountSmall('amount[]', data.amount) }} + + {{ Form.select('budget[]', budgets, data.budget_id, {class: 'form-control'}) }} + + {{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }} +
+

+ Add another split +

+
+
+
+
+ + +{% endblock %} +{% block scripts %} + + + +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/transactions/create.twig b/resources/views/transactions/create.twig index f263b38ff2..720d85cc84 100644 --- a/resources/views/transactions/create.twig +++ b/resources/views/transactions/create.twig @@ -32,20 +32,17 @@ {{ ExpandedForm.text('description') }} - - {{ ExpandedForm.select('account_id',accounts) }} + + {{ ExpandedForm.select('source_account_id',assetAccounts, null, {label: trans('form.asset_source_account')}) }} + + {{ ExpandedForm.text('source_account_name',null, {label: trans('form.revenue_account')}) }} - - {{ ExpandedForm.text('expense_account') }} + + {{ ExpandedForm.text('destination_account_name',null, {label: trans('form.expense_account')}) }} - - {{ ExpandedForm.text('revenue_account') }} - - - - {{ ExpandedForm.select('account_from_id',accounts) }} - {{ ExpandedForm.select('account_to_id',accounts) }} + + {{ ExpandedForm.select('destination_account_id',assetAccounts, null, {label: trans('form.asset_destination_account')} ) }} From 0e3ccebd0bd19a9cc802609df5233e3505393102 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 20:59:28 +0200 Subject: [PATCH 053/206] First attempt at #142. Needs a lot of work still. --- app/Crud/Split/Journal.php | 203 +++++++++++++ app/Crud/Split/JournalInterface.php | 39 +++ .../Transaction/SplitController.php | 38 ++- app/Http/Requests/SplitJournalFormRequest.php | 85 ++++++ app/Http/routes.php | 1 + app/Models/Budget.php | 9 + app/Models/Category.php | 8 + app/Models/Transaction.php | 18 ++ app/Providers/CrudServiceProvider.php | 54 ++++ .../Journal/JournalRepository.php | 8 +- app/Support/Models/TagSupport.php | 5 + .../2016_04_25_093451_changes_for_v385.php | 31 ++ public/js/ff/charts.js | 17 -- public/js/ff/firefly.js | 15 + public/js/ff/split/journal/from-store.js | 49 ++++ resources/lang/en_US/firefly.php | 12 + resources/lang/en_US/form.php | 2 + resources/lang/en_US/list.php | 2 + .../views/split/journals/from-store.twig | 273 ++++++++++-------- 19 files changed, 714 insertions(+), 155 deletions(-) create mode 100644 app/Crud/Split/Journal.php create mode 100644 app/Crud/Split/JournalInterface.php create mode 100644 app/Http/Requests/SplitJournalFormRequest.php create mode 100644 app/Providers/CrudServiceProvider.php create mode 100644 public/js/ff/split/journal/from-store.js diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php new file mode 100644 index 0000000000..9d742e7ff5 --- /dev/null +++ b/app/Crud/Split/Journal.php @@ -0,0 +1,203 @@ +user = $user; + } + + /** + * @param array $data + * + * @return TransactionJournal + */ + public function storeJournal(array $data) : TransactionJournal + { + // find transaction type. + $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); + $journal = new TransactionJournal( + [ + 'user_id' => $this->user->id, + 'transaction_type_id' => $transactionType->id, + 'transaction_currency_id' => $data['currency_id'], + 'description' => $data['description'], + 'completed' => 0, + 'date' => $data['date'], + 'interest_date' => $data['interest_date'], + 'book_date' => $data['book_date'], + 'process_date' => $data['process_date'], + ] + ); + $journal->save(); + + return $journal; + } + + /** + * @param TransactionJournal $journal + * @param array $transaction + * + * @return Collection + */ + public function storeTransaction(TransactionJournal $journal, array $transaction): Collection + { + // store accounts (depends on type) + list($sourceAccount, $destinationAccount) = $this->storeAccounts($journal->transactionType->type, $transaction); + + // store transaction one way: + /** @var Transaction $one */ + $one = Transaction::create( // first transaction. + [ + 'account_id' => $sourceAccount->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $transaction['amount'] * -1, + ] + ); + + // store transaction the other way: + $two = Transaction::create( // first transaction. + [ + 'account_id' => $destinationAccount->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $transaction['amount'], + ] + ); + + // store or get category and connect: + if (strlen($transaction['category']) > 0) { + $category = Category::firstOrCreateEncrypted(['name' => $transaction['category'], 'user_id' => $journal->user_id]); + $one->categories()->save($category); + $two->categories()->save($category); + } + // store or get budget + if (intval($transaction['budget_id']) > 0) { + $budget = Budget::find($transaction['budget_id']); + $one->budgets()->save($budget); + $two->budgets()->save($budget); + } + + return new Collection([$one, $two]); + + } + + /** + * @param string $type + * @param array $transaction + * + * @return array + * @throws FireflyException + */ + private function storeAccounts(string $type, array $transaction): array + { + $sourceAccount = null; + $destinationAccount = null; + switch ($type) { + case TransactionType::WITHDRAWAL: + list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction); + break; + case TransactionType::DEPOSIT: + list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction); + break; + case TransactionType::TRANSFER: + $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first(); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first(); + break; + default: + throw new FireflyException('Cannot handle ' . e($type)); + } + + return [$sourceAccount, $destinationAccount]; + } + + /** + * @param array $data + * + * @return array + */ + private function storeDepositAccounts(array $data): array + { + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_destination_id'])->first(['accounts.*']); + + if (strlen($data['source_account_name']) > 0) { + $fromType = AccountType::where('type', 'Revenue account')->first(); + $fromAccount = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1] + ); + + return [$fromAccount, $destinationAccount]; + } else { + $fromType = AccountType::where('type', 'Cash account')->first(); + $fromAccount = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1] + ); + } + + return [$fromAccount, $destinationAccount]; + } + + /** + * @param array $data + * + * @return array + */ + private function storeWithdrawalAccounts(array $data): array + { + $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); + + if (strlen($data['destination_account_name']) > 0) { + $destinationType = AccountType::where('type', 'Expense account')->first(); + $destinationAccount = Account::firstOrCreateEncrypted( + [ + 'user_id' => $this->user->id, + 'account_type_id' => $destinationType->id, + 'name' => $data['destination_account_name'], + 'active' => 1, + ] + ); + + return [$sourceAccount, $destinationAccount]; + } + $destinationType = AccountType::where('type', 'Cash account')->first(); + $destinationAccount = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1] + ); + + return [$sourceAccount, $destinationAccount]; + + + } +} \ No newline at end of file diff --git a/app/Crud/Split/JournalInterface.php b/app/Crud/Split/JournalInterface.php new file mode 100644 index 0000000000..00c00a5cba --- /dev/null +++ b/app/Crud/Split/JournalInterface.php @@ -0,0 +1,39 @@ +find(intval($journalData['amount_currency_id_amount'])); + $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); if (!is_array($journalData)) { throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. } - // echo '
';
-        //        var_dump($journalData);
-        //        echo '
'; - // exit; - return view('split.journals.from-store', compact('currency', 'assetAccounts', 'budgets'))->with('data', $journalData); + return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets'))->with('data', $journalData); } - public function postJournalFromStore() + /** + * @param SplitJournalFormRequest $request + * @param JournalInterface $repository + * + * @return mixed + */ + public function postJournalFromStore(SplitJournalFormRequest $request, JournalInterface $repository) { + $data = $request->getSplitData(); + + // store an empty journal first. This will be the place holder. + $journal = $repository->storeJournal($data); + // Then, store each transaction individually. + + foreach ($data['transactions'] as $transaction) { + $transactions = $repository->storeTransaction($journal, $transaction); + } + + // TODO move to repository. + $journal->completed = true; + $journal->save(); + // forget temp journal data - // Session::forget('temporary_split_data'); + Session::forget('temporary_split_data'); + + // this is where we originally came from. + return redirect(session('transactions.create.url')); } } \ No newline at end of file diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php new file mode 100644 index 0000000000..3cb66f26f8 --- /dev/null +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -0,0 +1,85 @@ + $this->get('journal_description'), + 'currency_id' => intval($this->get('currency')), + 'source_account_id' => intval($this->get('source_account_id')), + 'date' => new Carbon($this->get('date')), + 'what' => $this->get('what'), + 'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null, + 'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null, + 'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null, + 'transactions' => [], + ]; + // description is leading because it is one of the mandatory fields. + foreach ($this->get('description') as $index => $description) { + $transaction = [ + 'description' => $description, + 'amount' => round($this->get('amount')[$index], 2), + 'budget_id' => $this->get('budget')[$index] ? $this->get('budget')[$index] : 0, + 'category' => $this->get('category')[$index] ?? '', + 'source_account_id' => intval($this->get('source_account_id')), + 'destination_account_name' => $this->get('destination_account_name')[$index] ?? '' + ]; + $data['transactions'][] = $transaction; + } + + return $data; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'journal_description' => 'required|between:1,255', + 'currency' => 'required|exists:transaction_currencies,id', + 'source_account_id' => 'numeric|belongsToUser:accounts,id', + 'what' => 'required|in:withdrawal,deposit,transfer', + 'date' => 'required|date', + 'interest_date' => 'date', + 'book_date' => 'date', + 'process_date' => 'date', + 'description.*' => 'required|between:1,255', + 'destination_account_name.*' => 'between:1,255', + 'amount.*' => 'required|numeric', + 'budget.*' => 'belongsToUser:budgets,id', + 'category.*' => 'between:1,255', + ]; + } +} \ No newline at end of file diff --git a/app/Http/routes.php b/app/Http/routes.php index 3a01ba5daf..d811da5f7f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -346,6 +346,7 @@ Route::group( * Split controller */ Route::get('/transaction/split', ['uses' => 'Transaction\SplitController@journalFromStore', 'as' => 'split.journal.from-store']); + Route::post('/transaction/split', ['uses' => 'Transaction\SplitController@postJournalFromStore', 'as' => 'split.journal.from-store.post']); /** * Tag Controller diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 092477fa6b..0d7205d9ae 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -34,6 +34,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereActive($value) * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereEncrypted($value) * @mixin \Eloquent + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions */ class Budget extends Model { @@ -136,6 +137,14 @@ class Budget extends Model return $this->belongsToMany('FireflyIII\Models\TransactionJournal', 'budget_transaction_journal', 'budget_id'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function transactions() + { + return $this->belongsToMany('FireflyIII\Models\Transaction', 'budget_transaction', 'budget_id'); + } + /** * @return BelongsTo */ diff --git a/app/Models/Category.php b/app/Models/Category.php index f3139fdf7f..325ba87e39 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -31,6 +31,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereUserId($value) * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereEncrypted($value) * @mixin \Eloquent + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions */ class Category extends Model { @@ -116,6 +117,13 @@ class Category extends Model { return $this->belongsToMany('FireflyIII\Models\TransactionJournal', 'category_transaction_journal', 'category_id'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function transactions() + { + return $this->belongsToMany('FireflyIII\Models\Transaction', 'category_transaction', 'category_id'); + } /** * @return BelongsTo diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index e8f8cd8097..8417d5850a 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -32,6 +32,8 @@ use Watson\Validating\ValidatingTrait; * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction whereDescription($value) * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction whereAmount($value) * @mixin \Eloquent + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $categories */ class Transaction extends Model { @@ -104,4 +106,20 @@ class Transaction extends Model { return $this->belongsTo('FireflyIII\Models\TransactionJournal'); } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function budgets() + { + return $this->belongsToMany('FireflyIII\Models\Budget'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function categories() + { + return $this->belongsToMany('FireflyIII\Models\Category'); + } } diff --git a/app/Providers/CrudServiceProvider.php b/app/Providers/CrudServiceProvider.php new file mode 100644 index 0000000000..26c2d59869 --- /dev/null +++ b/app/Providers/CrudServiceProvider.php @@ -0,0 +1,54 @@ +app->bind( + 'FireflyIII\Crud\Split\JournalInterface', + function (Application $app, array $arguments) { + if (!isset($arguments[0]) && $app->auth->check()) { + return app('FireflyIII\Crud\Split\Journal', [$app->auth->user()]); + } + if (!isset($arguments[0]) && !$app->auth->check()) { + throw new FireflyException('There is no user present.'); + } + + return app('FireflyIII\Crud\Split\Journal', $arguments); + } + ); + } +} diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 128996bd9b..cbb832a0f5 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -233,7 +233,6 @@ class JournalRepository implements JournalRepositoryInterface ); $journal->save(); - // store or get category if (strlen($data['category']) > 0) { $category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]); @@ -403,8 +402,8 @@ class JournalRepository implements JournalRepositoryInterface break; case TransactionType::TRANSFER: - $sourceAccount = Account::find($data['account_from_id']); - $destinationAccount = Account::find($data['account_to_id']); + $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first(); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first(); break; default: throw new FireflyException('Did not recognise transaction type.'); @@ -439,9 +438,10 @@ class JournalRepository implements JournalRepositoryInterface $fromAccount = Account::firstOrCreateEncrypted( ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1] ); + return [$fromAccount, $destinationAccount]; } else { - $fromType = AccountType::where('type', 'Cash account')->first(); + $fromType = AccountType::where('type', 'Cash account')->first(); $fromAccount = Account::firstOrCreateEncrypted( ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1] ); diff --git a/app/Support/Models/TagSupport.php b/app/Support/Models/TagSupport.php index 7ababbea04..8ff15d4cd0 100644 --- a/app/Support/Models/TagSupport.php +++ b/app/Support/Models/TagSupport.php @@ -6,6 +6,11 @@ namespace FireflyIII\Support\Models; use FireflyIII\Models\Tag; use Illuminate\Database\Eloquent\Model; +/** + * FireflyIII\Support\Models\TagSupport + * + * @mixin \Eloquent + */ class TagSupport extends Model { /** diff --git a/database/migrations/2016_04_25_093451_changes_for_v385.php b/database/migrations/2016_04_25_093451_changes_for_v385.php index d92282cde0..9783b045b9 100644 --- a/database/migrations/2016_04_25_093451_changes_for_v385.php +++ b/database/migrations/2016_04_25_093451_changes_for_v385.php @@ -32,6 +32,13 @@ class ChangesForV385 extends Migration // restore backup. Change unknowns to "monthly". $this->restoreRepeatFreqsToEnum($backup); + + // drop budget <> transaction table: + Schema::dropIfExists('budget_transaction'); + + // drop category <> transaction table: + Schema::dropIfExists('category_transaction'); + } /** @@ -84,6 +91,30 @@ class ChangesForV385 extends Migration $table->unique(['budget_id', 'startdate', 'repeat_freq'], 'unique_limit'); } ); + + // create NEW table for transactions <> budgets + Schema::create( + 'budget_transaction', function (Blueprint $table) { + $table->increments('id'); + $table->integer('budget_id')->unsigned(); + $table->integer('transaction_id')->unsigned(); + $table->foreign('budget_id')->references('id')->on('budgets')->onDelete('cascade'); + $table->foreign('transaction_id')->references('id')->on('transactions')->onDelete('cascade'); + $table->unique(['budget_id', 'transaction_id'], 'budid_tid_unique'); + } + ); + + // create NEW table for transactions <> categories + Schema::create( + 'category_transaction', function (Blueprint $table) { + $table->increments('id'); + $table->integer('category_id')->unsigned(); + $table->integer('transaction_id')->unsigned(); + $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); + $table->foreign('transaction_id')->references('id')->on('transactions')->onDelete('cascade'); + $table->unique(['category_id', 'transaction_id'], 'catid_tid_unique'); + } + ); } /** diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js index e6b00274cd..0c91277793 100644 --- a/public/js/ff/charts.js +++ b/public/js/ff/charts.js @@ -32,23 +32,6 @@ var colourSet = [ ]; -// Settings object that controls default parameters for library methods: -accounting.settings = { - currency: { - symbol: currencySymbol, // default currency symbol is '$' - format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below) - decimal: mon_decimal_point, // decimal point separator - thousand: mon_thousands_sep, // thousands separator - precision: frac_digits // decimal places - }, - number: { - precision: 0, // default precision on numbers is 0 - thousand: ",", - decimal: "." - } -}; - - var fillColors = []; var strokePointHighColors = []; diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index cedeaf41f3..5ce868262a 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -98,3 +98,18 @@ function currencySelect(e) { return false; } +// Settings object that controls default parameters for library methods: +accounting.settings = { + currency: { + symbol: currencySymbol, // default currency symbol is '$' + format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below) + decimal: mon_decimal_point, // decimal point separator + thousand: mon_thousands_sep, // thousands separator + precision: frac_digits // decimal places + }, + number: { + precision: 0, // default precision on numbers is 0 + thousand: ",", + decimal: "." + } +}; diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js new file mode 100644 index 0000000000..18aea1a0b9 --- /dev/null +++ b/public/js/ff/split/journal/from-store.js @@ -0,0 +1,49 @@ +/* + * from-store.js + * Copyright (C) 2016 thegrumpydictator@gmail.com + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +/* globals globalSum */ + +$(function () { + "use strict"; + $('.btn-do-split').click(cloneRow); + $('input[name="amount[]"]').on('input', calculateSum) +}); + +function cloneRow() { + "use strict"; + var source = $('.initial-row').clone(); + var count = $('.split-table tbody tr').length + 1; + source.removeClass('initial-row'); + source.find('.count').text('#' + count); + source.find('input[name="amount[]"]').val("").on('input', calculateSum); + + $('.split-table tbody').append(source); + + calculateSum(); + + return false; +} + +function calculateSum() { + "use strict"; + var sum = 0; + var set = $('input[name="amount[]"]'); + for (var i = 0; i < set.length; i++) { + var current = $(set[i]); + sum += (current.val() == "" ? 0 : parseFloat(current.val())); + + } + console.log("Sum is now " + sum); + $('.amount-warning').remove(); + if (sum != originalSum) { + console.log(sum + ' does not match ' + originalSum); + var holder = $('#amount_holder'); + var par = holder.find('p.form-control-static'); + var amount = $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par); + } +} \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 2d3e3f6152..cf87d615bf 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -800,4 +800,16 @@ return [ 'user_administration' => 'User administration', 'list_all_users' => 'All users', 'all_users' => 'All users', + + // split a transaction: + 'transaction_meta_data' => 'Transaction meta-data', + 'transaction_dates' => 'Transaction dates', + 'splits' => 'Splits', + 'split_title_withdrawal' => 'Split your new withdrawal', + 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', + 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', + 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', + 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'add_another_split' => 'Add another split', + 'store_splitted_withdrawal' => 'Store splitted withdrawal', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 08eec339f7..24f17cc966 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -26,6 +26,8 @@ return [ 'account_to_id' => 'To account', 'asset_destination_account' => 'Asset account (destination)', 'asset_source_account' => 'Asset account (source)', + 'journal_description' => 'Description', + 'currency' => 'Currency', 'account_id' => 'Asset account', 'budget_id' => 'Budget', 'openingBalance' => 'Opening balance', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index ca4ab77a2d..09af7fb023 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -24,6 +24,8 @@ return [ 'matchesOn' => 'Matched on', 'matchingAmount' => 'Amount', 'lastMatch' => 'Last match', + 'split_number' => 'Split #', + 'destination' => 'Destination', 'expectedMatch' => 'Expected match', 'automatch' => 'Auto match?', 'repeat_freq' => 'Repeats', diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig index 363f2ebd6e..8226468056 100644 --- a/resources/views/split/journals/from-store.twig +++ b/resources/views/split/journals/from-store.twig @@ -4,163 +4,184 @@ {{ Breadcrumbs.renderIfExists }} {% endblock %} {% block content %} -
-
-
-
-
-

Split your new [type]

+ {{ Form.open({'class' : 'form-horizontal','id' : 'store','url' : route('split.journal.from-store.post')}) }} + +
+
+
+
+

{{ ('split_title_'~data.what)|_ }}

-
- -
+
+
-
-

- Firefly supports the "splitting" of an [expense / withdrawal / transfer]. +

+
+

+ {{ ('split_intro_one_'~data.what)|_ }} +

+

+ {{ ('split_intro_two_'~data.what)|_ }} +

+

+ {{ trans(('firefly.split_intro_three_'~data.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} +

+
-
-
-
-
-

Transaction meta-data

+
+
+
+
+
+

{{ 'transaction_meta_data'|_ }}

-
- -
-
-
- {{ ExpandedForm.text('journal_description', data.description) }} - {{ ExpandedForm.staticText('amount', data.amount|formatAmount) }} - - {% if data.what == 'withdrawal' or data.what == 'transfer' %} - {{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.source_account_id]) }} - {% endif %} - - {% if data.what == 'deposit' or data.what == 'transfer' %} - {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }} - {% endif %} +
+
-
-
-
-
-

Transaction dates

- -
- -
-
-
- {{ ExpandedForm.date('date', data.date) }} - - {{ ExpandedForm.date('interest_date', data.interest_date) }} - - {{ ExpandedForm.date('book_date', data.book_date) }} - - {{ ExpandedForm.date('process_date', data.process_date) }} -
+
+ {{ ExpandedForm.text('journal_description', data.description) }} + {{ ExpandedForm.select('currency', currencies, data.amount_currency_id_amount) }} + {{ ExpandedForm.staticText('amount', data.amount|formatAmount) }} + + {% if data.what == 'withdrawal' or data.what == 'transfer' %} + {{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.source_account_id]) }} + + {% endif %} + + {% if data.what == 'deposit' or data.what == 'transfer' %} + {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }} + + {% endif %}
-
-
-
-
-

Splits

+
+
+
+

{{ 'transaction_dates'|_ }}

-
- -
+
+
-
-

- Split your [x] in as many things as you want. By default the transaction will not split, there is just one entry. - Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you - do, Firefly will warn you but not correct you. -

- - - - - - - {% if data.what == 'deposit' %} - - {% endif %} - - {% if data.what == 'withdrawal' %} - - {% endif %} - - {% if data.what == 'withdrawal' %} - - {% endif %} - - {% if data.what == 'transfer' %} - - {% endif %} - - - - - - - {% if data.what == 'deposit' %} - - {% endif %} - {% if data.what == 'withdrawal' %} - - {% endif %} - - {% if data.what == 'withdrawal' %} - - {% endif %} + +
+ {{ ExpandedForm.date('date', data.date) }} + + {{ ExpandedForm.date('interest_date', data.interest_date) }} + + {{ ExpandedForm.date('book_date', data.book_date) }} + + {{ ExpandedForm.date('process_date', data.process_date) }} +
+ + + +
+
+
+
+

{{ 'splits'|_ }}

+ +
+ +
+
+
+

+ {{ ('split_table_intro_'~data.what)|_ }} +

+
Split #DescriptionSourceDestinationAmountBudgetCategoryPiggy bank
#1{{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }} - {{ Form.input('text', 'source_account_name[]', data.source_account_name, {class: 'form-control'}) }} - - {{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }} - - {{ ExpandedForm.amountSmall('amount[]', data.amount) }} - - {{ Form.select('budget[]', budgets, data.budget_id, {class: 'form-control'}) }} -
+ + + + + + {% if data.what == 'deposit' %} + + {% endif %} + + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'transfer' %} + + {% endif %} + + + + + + + {% if data.what == 'deposit' %} - - -
{{ trans('list.split_number') }}{{ trans('list.description') }}{{ trans('list.source') }}{{ trans('list.destination') }}{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}{{ trans('list.piggy_bank') }}
#1{{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }} - {{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }} + {{ Form.input('text', 'source_account_name[]', data.source_account_name, {class: 'form-control'}) }}
-

- Add another split -

-
+ {% endif %} + {% if data.what == 'withdrawal' %} + + {{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }} + + {% endif %} + + {{ Form.input('number', 'amount[]', data.amount, {class: 'form-control', autocomplete: 'off', step: 'any', min:'0.01'}) }} + + {% if data.what == 'withdrawal' %} + + {{ Form.select('budget[]', budgets, data.budget_id, {class: 'form-control'}) }} + + {% endif %} + + {{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }} + + + + +

+
+ {{ 'add_another_split'|_ }} +

+

+ +

+
{% endblock %} {% block scripts %} + {% endblock %} {% block styles %} {% endblock %} From 42c3d1fa686b13bcd477491f59f43fb54c90dd1e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 21:36:59 +0200 Subject: [PATCH 054/206] Expanding support for split transactions #142 --- app/Models/TransactionJournal.php | 10 +- .../Account/AccountRepository.php | 37 +++--- .../Models/TransactionJournalSupport.php | 35 ++++++ public/lib/font-awesome/css/font-awesome.css | 108 ++++++++++++++++-- .../lib/font-awesome/css/font-awesome.min.css | 4 +- public/lib/font-awesome/fonts/FontAwesome.otf | Bin 109688 -> 123112 bytes .../fonts/fontawesome-webfont.eot | Bin 70807 -> 75220 bytes .../fonts/fontawesome-webfont.svg | 54 +++++++-- .../fonts/fontawesome-webfont.ttf | Bin 142072 -> 150920 bytes .../fonts/fontawesome-webfont.woff | Bin 83588 -> 89076 bytes .../fonts/fontawesome-webfont.woff2 | Bin 66624 -> 70728 bytes resources/views/list/journals.twig | 6 +- 12 files changed, 212 insertions(+), 42 deletions(-) diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index c87e3f426d..9a88715ff5 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -3,6 +3,7 @@ use Auth; use Carbon\Carbon; use Crypt; +use DB; use FireflyIII\Support\Models\TransactionJournalSupport; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -94,7 +95,8 @@ class TransactionJournal extends TransactionJournalSupport 'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent. 'transaction_currencies.code AS transaction_currency_code', // all for destination: - 'destination.amount AS destination_amount', // is always positive + //'destination.amount AS destination_amount', // is always positive + // DB::raw('SUM(`destination`.`amount`) as `destination_amount`'), 'destination_account.id AS destination_account_id', 'destination_account.name AS destination_account_name', 'destination_acct_type.type AS destination_account_type', @@ -378,6 +380,12 @@ class TransactionJournal extends TransactionJournalSupport $query->leftJoin('account_types as source_acct_type', 'source_account.account_type_id', '=', 'source_acct_type.id') ->orderBy('transaction_journals.date', 'DESC')->orderBy('transaction_journals.order', 'ASC')->orderBy('transaction_journals.id', 'DESC'); + // something else: + $query->where(DB::raw('`destination`.`amount` * -1'),'=',DB::raw('`source`.`amount`')); + + // group: + $query->groupBy('transaction_journals.id'); + $query->with(['categories', 'budgets', 'attachments', 'bill']); diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index be72843096..2157e405aa 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -223,9 +223,6 @@ class AccountRepository implements AccountRepositoryInterface } /** - * This method is used on the front page where (in turn) its viewed journals-tiny.php which (in turn) - * is almost the only place where formatJournal is used. Aka, we can use some custom querying to get some specific. - * fields using left joins. * * @param Account $account * @param Carbon $start @@ -237,18 +234,21 @@ class AccountRepository implements AccountRepositoryInterface { $set = $this->user ->transactionjournals() - ->with(['transactions']) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')->where('accounts.id', $account->id) - ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->expanded() + ->where('source_account.id', $account->id) + // ->with(['transactions']) + // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + // ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')->where('accounts.id', $account->id) + // ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id') + // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') ->before($end) ->after($start) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') + // ->orderBy('transaction_journals.date', 'DESC') + // ->orderBy('transaction_journals.order', 'ASC') + // ->orderBy('transaction_journals.id', 'DESC') ->take(10) - ->get(['transaction_journals.*', 'transaction_currencies.symbol', 'transaction_types.type']); + // ->get(['transaction_journals.*', 'transaction_currencies.symbol', 'transaction_types.type']); + ->get(TransactionJournal::queryFields()); return $set; } @@ -291,14 +291,15 @@ class AccountRepository implements AccountRepositoryInterface $query = $this->user ->transactionJournals() ->expanded() - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $account->id) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC'); + ->where( + function (Builder $q) use ($account) { + $q->where('source_account.id', $account->id); + $q->orWhere('destination_account.id', $account->id); + } + ); $count = $query->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS); + $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); return $paginator; diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index 8786338f0a..23f55c5c0a 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -12,6 +12,7 @@ namespace FireflyIII\Support\Models; use Carbon\Carbon; +use DB; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; @@ -33,6 +34,7 @@ class TransactionJournalSupport extends Model * @param TransactionJournal $journal * * @return string + * @throws FireflyException */ public static function amount(TransactionJournal $journal): string { @@ -44,6 +46,14 @@ class TransactionJournalSupport extends Model return $cache->get(); } + if ($journal->isWithdrawal() && !is_null($journal->source_amount)) { + return $journal->source_amount; + } + if ($journal->isDeposit() && !is_null($journal->destination_amount)) { + return $journal->destination_amount; + } + + // breaks if > 2 $transaction = $journal->transactions->sortByDesc('amount')->first(); if (is_null($transaction)) { Log::error('Transaction journal #' . $journal->id . ' has ZERO transactions (or they have been deleted).'); @@ -219,6 +229,31 @@ class TransactionJournalSupport extends Model return 0; } + /** + * @return array + */ + public static function queryFields(): array + { + return [ + 'transaction_journals.*', + 'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent. + 'transaction_currencies.code AS transaction_currency_code', + // all for destination: + //'destination.amount AS destination_amount', // is always positive + DB::raw('SUM(`destination`.`amount`) as `destination_amount`'), + 'destination_account.id AS destination_account_id', + 'destination_account.name AS destination_account_name', + 'destination_acct_type.type AS destination_account_type', + // all for source: + //'source.amount AS source_amount', // is always negative + DB::raw('SUM(`source`.`amount`) as `source_amount`'), + 'source_account.id AS source_account_id', + 'source_account.name AS source_account_name', + 'source_acct_type.type AS source_account_type', + DB::raw('COUNT(`destination`.`id`) + COUNT(`source`.`id`) as `count_transactions`') + ]; + } + /** * @param TransactionJournal $journal * diff --git a/public/lib/font-awesome/css/font-awesome.css b/public/lib/font-awesome/css/font-awesome.css index b2a5fe2f25..bb0fe51adb 100644 --- a/public/lib/font-awesome/css/font-awesome.css +++ b/public/lib/font-awesome/css/font-awesome.css @@ -1,13 +1,13 @@ /*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.6.1 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('../fonts/fontawesome-webfont.eot?v=4.5.0'); - src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg'); + src: url('../fonts/fontawesome-webfont.eot?v=4.6.1'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.1') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.6.1') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.6.1') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.6.1') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.6.1#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } @@ -118,31 +118,31 @@ } } .fa-rotate-90 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; -webkit-transform: scale(-1, 1); -ms-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; -webkit-transform: scale(1, -1); -ms-transform: scale(1, -1); transform: scale(1, -1); @@ -2084,3 +2084,95 @@ .fa-percent:before { content: "\f295"; } +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/public/lib/font-awesome/css/font-awesome.min.css b/public/lib/font-awesome/css/font-awesome.min.css index d0603cb4b0..885b38403e 100644 --- a/public/lib/font-awesome/css/font-awesome.min.css +++ b/public/lib/font-awesome/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.6.1 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.1');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.1') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.1') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.1') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.1') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.1#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/public/lib/font-awesome/fonts/FontAwesome.otf b/public/lib/font-awesome/fonts/FontAwesome.otf index 3ed7f8b48ad9bfab52eb03822fefcd6b77d2e680..59853bcda7308254f58c2d74053f3ba55c21b9fb 100644 GIT binary patch delta 58928 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 delta 45391 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 I**i1;KdWH;)&Kwi diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.eot b/public/lib/font-awesome/fonts/fontawesome-webfont.eot index 9b6afaedc0fd7aaf927a07f82da9c11022251b8b..96f92f9b83bfd1008a197f059e4806ae25327125 100644 GIT binary patch literal 75220 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 diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.svg b/public/lib/font-awesome/fonts/fontawesome-webfont.svg index d05688e9e2..5a5f0ecd46 100644 --- a/public/lib/font-awesome/fonts/fontawesome-webfont.svg +++ b/public/lib/font-awesome/fonts/fontawesome-webfont.svg @@ -169,7 +169,7 @@ - + @@ -178,7 +178,7 @@ - + @@ -484,7 +484,7 @@ - + @@ -641,15 +641,45 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.ttf b/public/lib/font-awesome/fonts/fontawesome-webfont.ttf index 26dea7951a73079223b50653c455c5adf46a4648..86784df961f954a23a3a5afa57f07ebee0396192 100644 GIT binary patch delta 11686 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 delta 2859 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�+by+-*6b4 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 E10;|Fr~m)} delta 82781 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 literal 66624 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 {{ journal.description }} {% if journal.attachments|length > 0 %} - {% endif %} + {% if journal.count_transactions > 2 %} + X + + {% endif %} From 94a79876cee99fc6df6d437ec3fc0dd69e169506 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 21:49:18 +0200 Subject: [PATCH 055/206] View for split transactions. #142 --- resources/views/list/journals.twig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/views/list/journals.twig b/resources/views/list/journals.twig index 2823109ffd..2e5913bf47 100644 --- a/resources/views/list/journals.twig +++ b/resources/views/list/journals.twig @@ -52,8 +52,7 @@ title="{{ Lang.choice('firefly.nr_of_attachments', journal.attachments|length, {count: journal.attachments|length}) }}"> {% endif %} {% if journal.count_transactions > 2 %} - X - + {% endif %} From 9baadd3793ab56ffc2ae7d41519ad587bd179833 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 21:52:15 +0200 Subject: [PATCH 056/206] Use other method of collecting query fields. #142 --- app/Http/Controllers/TagController.php | 2 +- app/Models/TransactionJournal.php | 22 ------------------- .../Account/AccountRepository.php | 6 ++--- app/Repositories/Bill/BillRepository.php | 2 +- app/Repositories/Budget/BudgetRepository.php | 12 +++++----- .../Category/SingleCategoryRepository.php | 6 ++--- .../Journal/JournalRepository.php | 4 ++-- app/Support/Binder/JournalList.php | 2 +- app/Support/Search/Search.php | 4 ++-- 9 files changed, 19 insertions(+), 41 deletions(-) diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index a57e76429e..b873c84e4b 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -221,7 +221,7 @@ class TagController extends Controller $subTitle = $tag->tag; $subTitleIcon = 'fa-tag'; /** @var Collection $journals */ - $journals = $tag->transactionjournals()->expanded()->get(TransactionJournal::QUERYFIELDS); + $journals = $tag->transactionjournals()->expanded()->get(TransactionJournal::queryFields()); $sum = $journals->sum( function (TransactionJournal $journal) { diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 9a88715ff5..91adf1e9a3 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -85,28 +85,6 @@ class TransactionJournal extends TransactionJournalSupport { use SoftDeletes, ValidatingTrait; - /** - * Fields which queries must load. - * ['transaction_journals.*', 'transaction_currencies.symbol', 'transaction_types.type'] - */ - const QUERYFIELDS - = [ - 'transaction_journals.*', - 'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent. - 'transaction_currencies.code AS transaction_currency_code', - // all for destination: - //'destination.amount AS destination_amount', // is always positive - // DB::raw('SUM(`destination`.`amount`) as `destination_amount`'), - 'destination_account.id AS destination_account_id', - 'destination_account.name AS destination_account_name', - 'destination_acct_type.type AS destination_account_type', - // all for source: - 'source.amount AS source_amount', // is always negative - 'source_account.id AS source_account_id', - 'source_account.name AS source_account_name', - 'source_acct_type.type AS source_account_type', - - ]; /** @var array */ protected $dates = ['created_at', 'updated_at', 'date', 'deleted_at', 'interest_date', 'book_date', 'process_date']; /** @var array */ diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 2157e405aa..44d9bee43f 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -183,7 +183,7 @@ class AccountRepository implements AccountRepositoryInterface ->where('destination_account.id', $account->id) ->whereIn('source_account.id', $ids) ->after($start) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); return $journals; } @@ -273,7 +273,7 @@ class AccountRepository implements AccountRepositoryInterface ->where('source_account.id', $account->id) ->whereIn('destination_account.id', $ids) ->after($start) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); return $journals; } @@ -328,7 +328,7 @@ class AccountRepository implements AccountRepositoryInterface ->after($start) ->before($end); - $set = $query->get(TransactionJournal::QUERYFIELDS); + $set = $query->get(TransactionJournal::queryFields()); return $set; } diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index cc891e8b5e..fbae113952 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -319,7 +319,7 @@ class BillRepository implements BillRepositoryInterface ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC'); $count = $query->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS); + $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); return $paginator; diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index e88606a49c..ef6606d34c 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -93,7 +93,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->before($end) ->after($start) ->where('source_account.id', $account->id) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); } /** @@ -191,7 +191,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->whereNull('budget_transaction_journal.id') ->before($end) ->after($start) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); } /** @@ -448,7 +448,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->expanded() ->where('transaction_types.type', TransactionType::WITHDRAWAL) ->whereIn('source_account.id', $ids) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); return $set; } @@ -545,7 +545,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn } - $set = $setQuery->get(TransactionJournal::QUERYFIELDS); + $set = $setQuery->get(TransactionJournal::queryFields()); $count = $countQuery->count(); @@ -623,7 +623,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->after($start); $count = $query->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS); + $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); return $paginator; @@ -649,7 +649,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->whereNull('budget_transaction_journal.id') ->before($end) ->after($start) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); } /** diff --git a/app/Repositories/Category/SingleCategoryRepository.php b/app/Repositories/Category/SingleCategoryRepository.php index 127f212262..5f0ec5b771 100644 --- a/app/Repositories/Category/SingleCategoryRepository.php +++ b/app/Repositories/Category/SingleCategoryRepository.php @@ -157,7 +157,7 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate ->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC') - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); } @@ -180,7 +180,7 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate ->expanded() ->whereIn('source_account.id', $ids) ->whereNotIn('destination_account.id', $ids) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); } /** @@ -203,7 +203,7 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate ->expanded() ->take($pageSize) ->offset($offset) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); } /** diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index cbb832a0f5..c539eee3bc 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -121,7 +121,7 @@ class JournalRepository implements JournalRepositoryInterface ->orderBy('date', 'DESC') ->orderBy('order', 'ASC') ->orderBy('id', 'DESC') - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); return $set; } @@ -153,7 +153,7 @@ class JournalRepository implements JournalRepositoryInterface $count = $query->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::QUERYFIELDS); + $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); return $journals; diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php index f673175db4..8c312f535f 100644 --- a/app/Support/Binder/JournalList.php +++ b/app/Support/Binder/JournalList.php @@ -37,7 +37,7 @@ class JournalList implements BinderInterface $object = TransactionJournal::whereIn('transaction_journals.id', $ids) ->expanded() ->where('transaction_journals.user_id', Auth::user()->id) - ->get(TransactionJournal::QUERYFIELDS); + ->get(TransactionJournal::queryFields()); if ($object->count() > 0) { return $object; diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index deb68245dd..b356271ccd 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -109,10 +109,10 @@ class Search implements SearchInterface $q->orWhere('transaction_journals.description', 'LIKE', '%' . e($word) . '%'); } } - )->get(TransactionJournal::QUERYFIELDS); + )->get(TransactionJournal::queryFields()); // encrypted - $all = Auth::user()->transactionjournals()->expanded()->where('transaction_journals.encrypted', 1)->get(TransactionJournal::QUERYFIELDS); + $all = Auth::user()->transactionjournals()->expanded()->where('transaction_journals.encrypted', 1)->get(TransactionJournal::queryFields()); $set = $all->filter( function (TransactionJournal $journal) use ($words) { foreach ($words as $word) { From e4f45b5370bc8d7e97243038c2fa0a38d71718fa Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Apr 2016 22:00:24 +0200 Subject: [PATCH 057/206] List of categories will check transactions as well. #142 --- .../Category/SingleCategoryRepository.php | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/Repositories/Category/SingleCategoryRepository.php b/app/Repositories/Category/SingleCategoryRepository.php index 5f0ec5b771..1c6e35f4b9 100644 --- a/app/Repositories/Category/SingleCategoryRepository.php +++ b/app/Repositories/Category/SingleCategoryRepository.php @@ -209,20 +209,36 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate /** * @param Category $category * - * @return Carbon|null + * @return Carbon */ public function getLatestActivity(Category $category): Carbon { + $first = new Carbon('1900-01-01'); + $second = new Carbon('1900-01-01'); $latest = $category->transactionjournals() ->orderBy('transaction_journals.date', 'DESC') ->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.id', 'DESC') ->first(); if ($latest) { - return $latest->date; + $first = $latest->date; } - return new Carbon('1900-01-01'); + // could also be a transaction, nowadays: + $latestTransaction = $category->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->first(['transactions.*', 'transaction_journals.date']); + if ($latestTransaction) { + $second = new Carbon($latestTransaction->date); + } + if ($first > $second) { + return $first; + } + + return $second; } /** From 11ea4b6d478c90030876326b32901d0046446b13 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 07:15:28 +0200 Subject: [PATCH 058/206] Fix migrations. --- config/firefly.php | 2 +- ...=> 2016_04_25_093451_changes_for_v390.php} | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) rename database/migrations/{2016_04_25_093451_changes_for_v385.php => 2016_04_25_093451_changes_for_v390.php} (91%) diff --git a/config/firefly.php b/config/firefly.php index 393aa77c39..fe4724a449 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -2,7 +2,7 @@ return [ 'chart' => 'chartjs', - 'version' => '3.8.4', + 'version' => '3.9.0', 'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'], 'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'csv_import_enabled' => true, diff --git a/database/migrations/2016_04_25_093451_changes_for_v385.php b/database/migrations/2016_04_25_093451_changes_for_v390.php similarity index 91% rename from database/migrations/2016_04_25_093451_changes_for_v385.php rename to database/migrations/2016_04_25_093451_changes_for_v390.php index 9783b045b9..0afc024bf1 100644 --- a/database/migrations/2016_04_25_093451_changes_for_v385.php +++ b/database/migrations/2016_04_25_093451_changes_for_v390.php @@ -5,9 +5,9 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** - * Class ChangesForV385 + * Class ChangesForV390 */ -class ChangesForV385 extends Migration +class ChangesForV390 extends Migration { /** * Reverse the migrations. @@ -16,6 +16,14 @@ class ChangesForV385 extends Migration */ public function down() { + // restore removed unique index. Recreate it the correct way: + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->unique(['budget_id', 'startdate', 'repeat_freq'], 'unique_bl_combi'); + } + ); + + $backup = $this->backupRepeatFreqsFromString(); // drop string and create enum field @@ -48,15 +56,21 @@ class ChangesForV385 extends Migration */ public function up() { - // remove an index. + // // remove an index. + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->dropForeign('bid_foreign'); + } + ); + Schema::table( 'budget_limits', function (Blueprint $table) { $table->dropUnique('unique_limit'); - $table->dropForeign('bid_foreign'); $table->dropUnique('unique_bl_combi'); } ); + // recreate foreign key: Schema::table( 'budget_limits', function (Blueprint $table) { @@ -65,7 +79,6 @@ class ChangesForV385 extends Migration ); - // backup values $backup = $this->backupRepeatFreqsFromEnum(); From 4ec6bcc8c7330965f612eb09b6797723bbcc7014 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 09:48:39 +0200 Subject: [PATCH 059/206] More code for split and small bug fix in attachment helper. --- app/Crud/Split/Journal.php | 8 ++- app/Helpers/Attachments/AttachmentHelper.php | 1 + .../Transaction/SplitController.php | 7 ++- app/Http/Requests/JournalFormRequest.php | 2 +- app/Http/Requests/SplitJournalFormRequest.php | 27 +++++---- .../Journal/JournalRepository.php | 2 +- database/seeds/DatabaseSeeder.php | 4 ++ database/seeds/SplitDataSeeder.php | 55 +++++++++++++++++++ resources/lang/en_US/firefly.php | 10 +++- .../views/split/journals/from-store.twig | 37 ++++++++++--- 10 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 database/seeds/SplitDataSeeder.php diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php index 9d742e7ff5..9315ba9193 100644 --- a/app/Crud/Split/Journal.php +++ b/app/Crud/Split/Journal.php @@ -85,6 +85,7 @@ class Journal implements JournalInterface 'account_id' => $sourceAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'] * -1, + 'description' => $transaction['description'], ] ); @@ -94,6 +95,7 @@ class Journal implements JournalInterface 'account_id' => $destinationAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'], + 'description' => $transaction['description'], ] ); @@ -133,8 +135,8 @@ class Journal implements JournalInterface list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction); break; case TransactionType::TRANSFER: - $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first(); - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first(); + $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['source_account_id'])->first(); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['destination_account_id'])->first(); break; default: throw new FireflyException('Cannot handle ' . e($type)); @@ -150,7 +152,7 @@ class Journal implements JournalInterface */ private function storeDepositAccounts(array $data): array { - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_destination_id'])->first(['accounts.*']); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']); if (strlen($data['source_account_name']) > 0) { $fromType = AccountType::where('type', 'Revenue account')->first(); diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 6fc6e3af0e..3433584545 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -251,6 +251,7 @@ class AttachmentHelper implements AttachmentHelperInterface $this->processFile($entry, $model); } } + return true; } diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 6a677f5b61..71eabcd273 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -18,6 +18,7 @@ use FireflyIII\Http\Requests\SplitJournalFormRequest; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use Log; use Session; /** @@ -40,8 +41,9 @@ class SplitController extends Controller /** @var BudgetRepositoryInterface $budgetRepository */ $budgetRepository = app(BudgetRepositoryInterface::class); + // expect data to be in session or in post? - $journalData = session('temporary_split_data'); + $journalData = session('temporary_split_data'); $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); @@ -49,6 +51,9 @@ class SplitController extends Controller throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. } + Log::debug('Journal data', $journalData); + + return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets'))->with('data', $journalData); diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 5b5a7905d7..8b8886b76b 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -39,7 +39,7 @@ class JournalFormRequest extends Request 'description' => $this->get('description'), 'source_account_id' => intval($this->get('source_account_id')), 'source_account_name' => $this->get('source_account_name') ?? '', - 'account_destination_id' => intval($this->get('account_destination_id')), + 'destination_account_id' => intval($this->get('destination_account_id')), 'destination_account_name' => $this->get('destination_account_name') ?? '', 'amount' => round($this->get('amount'), 2), 'user' => Auth::user()->id, diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index 3cb66f26f8..fc16ae7f87 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -35,25 +35,28 @@ class SplitJournalFormRequest extends Request public function getSplitData(): array { $data = [ - 'description' => $this->get('journal_description'), - 'currency_id' => intval($this->get('currency')), - 'source_account_id' => intval($this->get('source_account_id')), - 'date' => new Carbon($this->get('date')), - 'what' => $this->get('what'), - 'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null, - 'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null, - 'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null, - 'transactions' => [], + 'description' => $this->get('journal_description'), + 'currency_id' => intval($this->get('currency')), + 'source_account_id' => intval($this->get('source_account_id')), + 'source_account_name' => $this->get('source_account_name'), + 'date' => new Carbon($this->get('date')), + 'what' => $this->get('what'), + 'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null, + 'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null, + 'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null, + 'transactions' => [], ]; // description is leading because it is one of the mandatory fields. foreach ($this->get('description') as $index => $description) { $transaction = [ 'description' => $description, 'amount' => round($this->get('amount')[$index], 2), - 'budget_id' => $this->get('budget')[$index] ? $this->get('budget')[$index] : 0, + 'budget_id' => $this->get('budget')[$index] ? intval($this->get('budget')[$index]) : 0, 'category' => $this->get('category')[$index] ?? '', 'source_account_id' => intval($this->get('source_account_id')), - 'destination_account_name' => $this->get('destination_account_name')[$index] ?? '' + 'source_account_name' => $this->get('source_account_name'), + 'destination_account_id' => $this->get('destination_account_id')[$index] ? intval($this->get('destination_account_id')[$index]) : 0, + 'destination_account_name' => $this->get('destination_account_name')[$index] ?? '', ]; $data['transactions'][] = $transaction; } @@ -70,12 +73,14 @@ class SplitJournalFormRequest extends Request 'journal_description' => 'required|between:1,255', 'currency' => 'required|exists:transaction_currencies,id', 'source_account_id' => 'numeric|belongsToUser:accounts,id', + 'source_account_name.*' => 'between:1,255', 'what' => 'required|in:withdrawal,deposit,transfer', 'date' => 'required|date', 'interest_date' => 'date', 'book_date' => 'date', 'process_date' => 'date', 'description.*' => 'required|between:1,255', + 'destination_account_id.*' => 'numeric|belongsToUser:accounts,id', 'destination_account_name.*' => 'between:1,255', 'amount.*' => 'required|numeric', 'budget.*' => 'belongsToUser:budgets,id', diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index c539eee3bc..7e17636398 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -431,7 +431,7 @@ class JournalRepository implements JournalRepositoryInterface */ protected function storeDepositAccounts(array $data): array { - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_destination_id'])->first(['accounts.*']); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']); if (strlen($data['source_account_name']) > 0) { $fromType = AccountType::where('type', 'Revenue account')->first(); diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index bf4ec69932..45131e047b 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -26,6 +26,10 @@ class DatabaseSeeder extends Seeder if (App::environment() == 'testing' || App::environment() == 'local') { $this->call('TestDataSeeder'); } + // set up basic test data (as little as possible): + if (App::environment() == 'split') { + $this->call('SplitDataSeeder'); + } } } diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php new file mode 100644 index 0000000000..623ca15178 --- /dev/null +++ b/database/seeds/SplitDataSeeder.php @@ -0,0 +1,55 @@ + 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', - 'add_another_split' => 'Add another split', 'store_splitted_withdrawal' => 'Store splitted withdrawal', + + 'split_title_deposit' => 'Split your new deposit', + 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', + 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', + 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', + 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_deposit' => 'Store splitted deposit', + 'add_another_split' => 'Add another split', + ]; diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig index 8226468056..12cf3aad19 100644 --- a/resources/views/split/journals/from-store.twig +++ b/resources/views/split/journals/from-store.twig @@ -24,7 +24,11 @@ {{ ('split_intro_two_'~data.what)|_ }}

- {{ trans(('firefly.split_intro_three_'~data.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} + {% if data.what =='deposit' %} + {{ trans(('firefly.split_intro_three_'~data.what), {total: 500|formatAmount, split_one: 425|formatAmount, split_two: 75|formatAmount})|raw }} + {% else %} + {{ trans(('firefly.split_intro_three_'~data.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} + {% endif %}

- {% if data.what == 'deposit' or data.what == 'transfer' %} + + {% if data.what == 'deposit' %} + {{ ExpandedForm.staticText('revenue_account', data.source_account_name) }} + + {% endif %} + + {% if data.what == 'transfer' %} {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }} {% endif %} @@ -113,11 +122,14 @@ {{ trans('list.split_number') }} {{ trans('list.description') }} + - {% if data.what == 'withdrawal' %} + + {% if data.what == 'withdrawal' or data.what == 'deposit' %} {{ trans('list.destination') }} {% endif %} {{ trans('list.amount') }} @@ -134,16 +146,23 @@ #1 {{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }} - {% if data.what == 'deposit' %} - - {{ Form.input('text', 'source_account_name[]', data.source_account_name, {class: 'form-control'}) }} - - {% endif %} + + {% if data.what == 'withdrawal' %} {{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }} {% endif %} + + + {% if data.what == 'deposit' %} + + {{ Form.select('destination_account_id[]', assetAccounts, data.destination_account_id, {class: 'form-control'}) }} + + {% endif %} + + + {{ Form.input('number', 'amount[]', data.amount, {class: 'form-control', autocomplete: 'off', step: 'any', min:'0.01'}) }} From bdcd033952f1bd3610dcd36bf284b10f9745c9cf Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 12:46:21 +0200 Subject: [PATCH 060/206] Fix edit routine --- .../Controllers/TransactionController.php | 60 +++++++++---------- .../Journal/JournalRepository.php | 6 +- resources/views/transactions/edit.twig | 39 ++++++++---- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 1b251f9b10..74a35cc1b9 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -145,42 +145,37 @@ class TransactionController extends Controller /** @var PiggyBankRepositoryInterface $piggyRepository */ $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); - $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); - $budgetList = ExpandedForm::makeSelectList($budgetRepository->getActiveBudgets()); - $piggyBankList = ExpandedForm::makeSelectList($piggyRepository->getPiggyBanks()); - $budgetList[0] = trans('firefly.no_budget'); - $piggyBankList[0] = trans('form.noPiggybank'); - $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); - $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); - $uploadSize = min($maxFileSize, $maxPostSize); - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); - $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks()); + $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); + $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); + $uploadSize = min($maxFileSize, $maxPostSize); + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); $preFilled = [ - 'date' => TransactionJournal::dateAsString($journal), - 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), - 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), - 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'), - 'category' => TransactionJournal::categoryAsString($journal), - 'budget_id' => TransactionJournal::budgetId($journal), - 'piggy_bank_id' => TransactionJournal::piggyBankId($journal), - 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), - 'account_from_id' => TransactionJournal::sourceAccount($journal)->id, - 'account_to_id' => TransactionJournal::destinationAccount($journal)->id, - 'amount' => TransactionJournal::amountPositive($journal), + 'date' => TransactionJournal::dateAsString($journal), + 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), + 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), + 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'), + 'category' => TransactionJournal::categoryAsString($journal), + 'budget_id' => TransactionJournal::budgetId($journal), + 'piggy_bank_id' => TransactionJournal::piggyBankId($journal), + 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), + 'source_account_id' => TransactionJournal::sourceAccount($journal)->id, + 'source_account_name' => TransactionJournal::sourceAccount($journal)->name, + 'destination_account_id' => TransactionJournal::destinationAccount($journal)->id, + 'destination_account_name' => TransactionJournal::destinationAccount($journal)->name, + 'amount' => TransactionJournal::amountPositive($journal), ]; - if ($journal->isWithdrawal()) { - $preFilled['account_id'] = TransactionJournal::sourceAccount($journal)->id; - if (TransactionJournal::destinationAccountTypeStr($journal) != 'Cash account') { - $preFilled['expense_account'] = TransactionJournal::destinationAccount($journal)->name; - } - } else { - $preFilled['account_id'] = TransactionJournal::destinationAccount($journal)->id; - if (TransactionJournal::sourceAccountTypeStr($journal) != 'Cash account') { - $preFilled['revenue_account'] = TransactionJournal::sourceAccount($journal)->name; - } + if ($journal->isWithdrawal() && TransactionJournal::destinationAccountTypeStr($journal) == 'Cash account') { + $preFilled['destination_account_name'] = ''; + } + if ($journal->isDeposit() && TransactionJournal::sourceAccountTypeStr($journal) == 'Cash account') { + $preFilled['source_account_name'] = ''; } @@ -195,7 +190,7 @@ class TransactionController extends Controller Session::forget('transactions.edit.fromUpdate'); - return view('transactions.edit', compact('journal', 'uploadSize', 'accountList', 'what', 'budgetList', 'piggyBankList', 'subTitle'))->with( + return view('transactions.edit', compact('journal', 'uploadSize', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle'))->with( 'data', $preFilled ); } @@ -484,7 +479,6 @@ class TransactionController extends Controller */ public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal) { - $journalData = $request->getJournalData(); $repository->update($journal, $journalData); diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 7e17636398..2f38596910 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -305,7 +305,7 @@ class JournalRepository implements JournalRepositoryInterface $journal->budgets()->detach(); if (intval($data['budget_id']) > 0) { /** @var \FireflyIII\Models\Budget $budget */ - $budget = Budget::find($data['budget_id']); + $budget = Budget::where('user_id', $this->user->id)->where('id', $data['budget_id'])->first(); $journal->budgets()->save($budget); } @@ -402,8 +402,8 @@ class JournalRepository implements JournalRepositoryInterface break; case TransactionType::TRANSFER: - $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first(); - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first(); + $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(); + $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(); break; default: throw new FireflyException('Did not recognise transaction type.'); diff --git a/resources/views/transactions/edit.twig b/resources/views/transactions/edit.twig index 03b09a1a77..03e8e286d3 100644 --- a/resources/views/transactions/edit.twig +++ b/resources/views/transactions/edit.twig @@ -12,6 +12,20 @@ + {% if errors.all|length > 0 %} +
+
+

Errors

+
    + {% for err in errors.all %} +
  • {{ err }}
  • + {% endfor %} +
+
+
+ {% endif %} + +
@@ -22,25 +36,24 @@ {{ ExpandedForm.text('description',journal.description) }} - - {% if what == 'deposit' or what == 'withdrawal' %} - {{ ExpandedForm.select('account_id',accountList,data['account_id']) }} + + {% if what == 'transfer' or what == 'withdrawal' %} + {{ ExpandedForm.select('source_account_id',assetAccounts, data.source_account_id, {label: trans('form.asset_source_account')}) }} {% endif %} - - {% if what == 'withdrawal' %} - {{ ExpandedForm.text('expense_account',data['expense_account']) }} - {% endif %} - - + {% if what == 'deposit' %} - {{ ExpandedForm.text('revenue_account',data['revenue_account']) }} + {{ ExpandedForm.text('source_account_name',data.source_account_name, {label: trans('form.revenue_account')}) }} {% endif %} - + + {% if what == 'withdrawal' %} + {{ ExpandedForm.text('destination_account_name',data.destination_account_name, {label: trans('form.expense_account')}) }} + {% endif %} + + {% if what == 'transfer' %} - {{ ExpandedForm.select('account_from_id',accountList,data['account_from_id']) }} - {{ ExpandedForm.select('account_to_id',accountList,data['account_to_id']) }} + {{ ExpandedForm.select('destination_account_id',assetAccounts, data.destination_account_id, {label: trans('form.asset_destination_account')} ) }} {% endif %} From c05c6e72c0e2e80f80705510c461aa559f4a1ea8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 16:36:58 +0200 Subject: [PATCH 061/206] Display budget and category if relevant. --- resources/views/transactions/show.twig | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index a15a52aafe..c139094dde 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -160,9 +160,9 @@

{{ t.account.name }}

- +
- + @@ -179,10 +179,26 @@ {% if t.description %} - + {% endif %} + {% if t.categories[0] %} + + + + + {% endif %} + {% if t.budgets[0] %} + + + + + {% endif %}
{{ 'account'|_ }}{{ 'account'|_ }} {{ t.account.name }}
Description{{ trans('form.description') }} {{ t.description }}
{{ 'category'|_ }} + {{ t.categories[0].name }} +
{{ 'budget'|_ }} + {{ t.budgets[0].name }} +
{% endfor %} From 9c5292962f0dc9688b282ab0d5ebb524ea529222 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 19:50:42 +0200 Subject: [PATCH 062/206] More stuff for splits. --- .../Transaction/SplitController.php | 8 +++++-- app/Http/Requests/SplitJournalFormRequest.php | 4 +++- .../Models/TransactionJournalSupport.php | 22 ++++++++----------- database/seeds/SplitDataSeeder.php | 1 + resources/lang/en_US/firefly.php | 11 +++++++++- resources/lang/en_US/form.php | 2 ++ resources/lang/en_US/list.php | 1 + .../views/split/journals/from-store.twig | 5 +++++ 8 files changed, 37 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 71eabcd273..913cd6cf97 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -18,6 +18,7 @@ use FireflyIII\Http\Requests\SplitJournalFormRequest; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Log; use Session; @@ -41,12 +42,15 @@ class SplitController extends Controller /** @var BudgetRepositoryInterface $budgetRepository */ $budgetRepository = app(BudgetRepositoryInterface::class); + /** @var PiggyBankRepositoryInterface $piggyBankRepository */ + $piggyBankRepository = app(PiggyBankRepositoryInterface::class); // expect data to be in session or in post? - $journalData = session('temporary_split_data'); + $journalData = session('temporary_split_data'); $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyBankRepository->getPiggyBanks()); if (!is_array($journalData)) { throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. } @@ -54,7 +58,7 @@ class SplitController extends Controller Log::debug('Journal data', $journalData); - return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets'))->with('data', $journalData); + return view('split.journals.from-store', compact('currencies', 'piggyBanks', 'assetAccounts', 'budgets'))->with('data', $journalData); } diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index fc16ae7f87..aad2dd05ba 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -55,7 +55,9 @@ class SplitJournalFormRequest extends Request 'category' => $this->get('category')[$index] ?? '', 'source_account_id' => intval($this->get('source_account_id')), 'source_account_name' => $this->get('source_account_name'), - 'destination_account_id' => $this->get('destination_account_id')[$index] ? intval($this->get('destination_account_id')[$index]) : 0, + 'destination_account_id' => isset($this->get('destination_account_id')[$index]) + ? intval($this->get('destination_account_id')[$index]) + : intval($this->get('destination_account_id')), 'destination_account_name' => $this->get('destination_account_name')[$index] ?? '', ]; $data['transactions'][] = $transaction; diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index 23f55c5c0a..71cea6ea8d 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -20,7 +20,6 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\CacheProperties; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Log; /** * Class TransactionJournalSupport @@ -47,27 +46,24 @@ class TransactionJournalSupport extends Model } if ($journal->isWithdrawal() && !is_null($journal->source_amount)) { + $cache->store($journal->source_amount); + return $journal->source_amount; } if ($journal->isDeposit() && !is_null($journal->destination_amount)) { + $cache->store($journal->destination_amount); + return $journal->destination_amount; } - // breaks if > 2 - $transaction = $journal->transactions->sortByDesc('amount')->first(); - if (is_null($transaction)) { - Log::error('Transaction journal #' . $journal->id . ' has ZERO transactions (or they have been deleted).'); - throw new FireflyException('Transaction journal #' . $journal->id . ' has ZERO transactions. Visit this page for a solution: https://git.io/vwPFY'); - } - $amount = $transaction->amount; - if ($journal->isWithdrawal()) { - $amount = bcmul($amount, '-1'); + $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount'); + if ($journal->isDeposit()) { + $amount = $amount * -1; } + $amount = strval($amount); $cache->store($amount); return $amount; - - } /** @@ -250,7 +246,7 @@ class TransactionJournalSupport extends Model 'source_account.id AS source_account_id', 'source_account.name AS source_account_name', 'source_acct_type.type AS source_account_type', - DB::raw('COUNT(`destination`.`id`) + COUNT(`source`.`id`) as `count_transactions`') + DB::raw('COUNT(`destination`.`id`) + COUNT(`source`.`id`) as `count_transactions`'), ]; } diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php index 623ca15178..43a5a7af72 100644 --- a/database/seeds/SplitDataSeeder.php +++ b/database/seeds/SplitDataSeeder.php @@ -51,5 +51,6 @@ class SplitDataSeeder extends Seeder TestData::createCategories($user); TestData::createExpenseAccounts($user); TestData::createRevenueAccounts($user); + TestData::createPiggybanks($user); } } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 71347147b3..ddf18123a8 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -818,6 +818,15 @@ return [ 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', 'store_splitted_deposit' => 'Store splitted deposit', - 'add_another_split' => 'Add another split', + + 'split_title_transfer' => 'Split your new transfer', + 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', + 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', + 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', + 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_transfer' => 'Store splitted transfer', + + + 'add_another_split' => 'Add another split', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 24f17cc966..f6ca7908bc 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -27,6 +27,8 @@ return [ 'asset_destination_account' => 'Asset account (destination)', 'asset_source_account' => 'Asset account (source)', 'journal_description' => 'Description', + 'split_journal' => 'Split this transaction', + 'split_journal_explanation' => 'Split this transaction in multiple parts', 'currency' => 'Currency', 'account_id' => 'Asset account', 'budget_id' => 'Budget', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index 09af7fb023..d894d21ee1 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -36,6 +36,7 @@ return [ 'book_date' => 'Book date', 'process_date' => 'Processing date', 'from' => 'From', + 'piggy_bank' => 'Piggy bank', 'to' => 'To', 'budget' => 'Budget', 'category' => 'Category', diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig index 12cf3aad19..8de8d22426 100644 --- a/resources/views/split/journals/from-store.twig +++ b/resources/views/split/journals/from-store.twig @@ -174,6 +174,11 @@ {{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }} + {% if data.what == 'transfer' %} + + {{ Form.select('piggy_bank_id[]', piggyBanks, data.piggy_bank_id, {class: 'form-control'}) }} + + {% endif %} From 1fd78523092eb609c85f447441dcc8049e0ff064 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 20:24:47 +0200 Subject: [PATCH 063/206] Fix query thing. --- app/Models/TransactionJournal.php | 2 + database/seeds/SplitDataSeeder.php | 159 +++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 91adf1e9a3..b6ed728650 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -325,6 +325,7 @@ class TransactionJournal extends TransactionJournalSupport */ public function scopeExpanded(EloquentBuilder $query) { + $query->distinct(); // left join transaction type: if (!self::isJoined($query, 'transaction_types')) { $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); @@ -363,6 +364,7 @@ class TransactionJournal extends TransactionJournalSupport // group: $query->groupBy('transaction_journals.id'); + $query->groupBy('source.id'); $query->with(['categories', 'budgets', 'attachments', 'bill']); diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php index 43a5a7af72..45e1999fca 100644 --- a/database/seeds/SplitDataSeeder.php +++ b/database/seeds/SplitDataSeeder.php @@ -16,6 +16,9 @@ declare(strict_types = 1); * of the MIT license. See the LICENSE file for details. */ +use Carbon\Carbon; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\Migration\TestData; use Illuminate\Database\Seeder; @@ -52,5 +55,161 @@ class SplitDataSeeder extends Seeder TestData::createExpenseAccounts($user); TestData::createRevenueAccounts($user); TestData::createPiggybanks($user); + + /* + * Create splitted expense of 66,- + */ + $today = new Carbon; + $today->subDays(6); + + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 1, // withdrawal + 'transaction_currency_id' => 1, + 'description' => 'Split Expense (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), + ] + ); + + // split in 6 transactions (multiple destinations). 22,- each + // source is TestData Checking Account. + // also attach some budgets and stuff. + $destinations = ['Albert Heijn', 'PLUS', 'Apple']; + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + $source = TestData::findAccount($user, 'TestData Checking Account'); + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); + + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-22', + + ] + ); + + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '22', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } + + // create splitted income of 99,- + $today->addDay(); + + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 2, // expense + 'transaction_currency_id' => 1, + 'description' => 'Split Income (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), + ] + ); + + // split in 6 transactions (multiple destinations). 22,- each + // source is TestData Checking Account. + // also attach some budgets and stuff. + $destinations = ['TestData Checking Account', 'TestData Savings', 'TestData Shared']; + $source = TestData::findAccount($user, 'Belastingdienst'); + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); + + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-33', + + ] + ); + + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '33', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } + + // create a splitted transfer of 57,- (19) + $today->addDay(); + + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 3, // transfer + 'transaction_currency_id' => 1, + 'description' => 'Split Transfer (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), + ] + ); + + + $source = TestData::findAccount($user, 'Emergencies'); + $destinations = ['TestData Checking Account', 'TestData Savings', 'TestData Shared']; + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); + + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-19', + + ] + ); + + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '19', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } + + } } From 55b8f035904f2f1a5a36b577d9c39ee6c43d61df Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 21:20:39 +0200 Subject: [PATCH 064/206] Some new data thing. --- app/Support/Migration/TestData.php | 81 ++++++--- database/seeds/SplitDataSeeder.php | 280 ++++++++++++++++------------- database/seeds/TestDataSeeder.php | 4 +- 3 files changed, 218 insertions(+), 147 deletions(-) diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index 924f535965..c383da8e09 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -44,38 +44,76 @@ class TestData /** - * @param User $user + * @param User $user + * @param array $assets * * @return bool */ - public static function createAssetAccounts(User $user): bool + public static function createAssetAccounts(User $user, array $assets): bool { - $assets = ['TestData Checking Account', 'TestData Savings', 'TestData Shared', 'TestData Creditcard', 'Emergencies', 'STE']; - // first two ibans match test-upload.csv - $ibans = ['NL11XOLA6707795988', 'NL96DZCO4665940223', 'NL81RCQZ7160379858', 'NL19NRAP2367994221', 'NL40UKBK3619908726', 'NL38SRMN4325934708']; - $assetMeta = [ - ['accountRole' => 'defaultAsset'], - ['accountRole' => 'savingAsset',], - ['accountRole' => 'sharedAsset',], - ['accountRole' => 'ccAsset', 'ccMonthlyPaymentDate' => '2015-05-27', 'ccType' => 'monthlyFull',], - ['accountRole' => 'savingAsset',], - ['accountRole' => 'savingAsset',], - ]; + if (count($assets) == 0) { + $assets = [ + [ + 'name' => 'TestData Checking Account', + 'iban' => 'NL11XOLA6707795988', + 'meta' => [ + 'accountRole' => 'defaultAsset', + ], + ], + [ + 'name' => 'TestData Savings', + 'iban' => 'NL96DZCO4665940223', + 'meta' => [ + 'accountRole' => 'savingAsset', + ], + ], + [ + 'name' => 'TestData Shared', + 'iban' => 'NL81RCQZ7160379858', + 'meta' => [ + 'accountRole' => 'sharedAsset', + ], + ], + [ + 'name' => 'TestData Creditcard', + 'iban' => 'NL19NRAP2367994221', + 'meta' => [ + 'accountRole' => 'ccAsset', + 'ccMonthlyPaymentDate' => '2015-05-27', + 'ccType' => 'monthlyFull', + ], + ], + [ + 'name' => 'Emergencies', + 'iban' => 'NL40UKBK3619908726', + 'meta' => [ + 'accountRole' => 'savingAsset', + ], + ], + [ + 'name' => 'STE', + 'iban' => 'NL38SRMN4325934708', + 'meta' => [ + 'accountRole' => 'savingAsset', + ], + ], + ]; + } - foreach ($assets as $index => $name) { + foreach ($assets as $index => $entry) { // create account: $account = Account::create( [ 'user_id' => $user->id, 'account_type_id' => 3, - 'name' => $name, + 'name' => $entry['name'], 'active' => 1, 'encrypted' => 1, - 'iban' => $ibans[$index], + 'iban' => $entry['iban'], ] ); - foreach ($assetMeta[$index] as $name => $value) { - AccountMeta::create(['account_id' => $account->id, 'name' => $name, 'data' => $value,]); + foreach ($entry['meta'] as $name => $value) { + AccountMeta::create(['account_id' => $account->id, 'name' => $name, 'data' => $value]); } } @@ -452,15 +490,16 @@ class TestData } /** - * @param User $user + * @param User $user + * @param string $accountName * * @return bool * */ - public static function createPiggybanks(User $user): bool + public static function createPiggybanks(User $user, string $accountName): bool { - $account = self::findAccount($user, 'TestData Savings'); + $account = self::findAccount($user, $accountName); $camera = PiggyBank::create( [ 'account_id' => $account->id, diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php index 45e1999fca..96c350e993 100644 --- a/database/seeds/SplitDataSeeder.php +++ b/database/seeds/SplitDataSeeder.php @@ -32,8 +32,6 @@ class SplitDataSeeder extends Seeder */ public function __construct() { - - } /** @@ -43,18 +41,50 @@ class SplitDataSeeder extends Seeder */ public function run() { + $skipWithdrawal = false; + $skipDeposit = true; + $skipTransfer = true; // start by creating all users: // method will return the first user. $user = TestData::createUsers(); - // create all kinds of static data: - TestData::createAssetAccounts($user); + $assets = [ + [ + 'name' => 'Checking Account', + 'iban' => 'NL11XOLA6707795988', + 'meta' => [ + 'accountRole' => 'defaultAsset', + ], + ], + [ + 'name' => 'Alternate Checking Account', + 'iban' => 'NL40UKBK3619908726', + 'meta' => [ + 'accountRole' => 'defaultAsset', + ], + ], + [ + 'name' => 'Savings Account', + 'iban' => 'NL96DZCO4665940223', + 'meta' => [ + 'accountRole' => 'savingAsset', + ], + ], + [ + 'name' => 'Shared Checking Account', + 'iban' => 'NL81RCQZ7160379858', + 'meta' => [ + 'accountRole' => 'sharedAsset', + ], + ], + ]; + TestData::createAssetAccounts($user, $assets); TestData::createBudgets($user); TestData::createCategories($user); TestData::createExpenseAccounts($user); TestData::createRevenueAccounts($user); - TestData::createPiggybanks($user); + TestData::createPiggybanks($user, 'Savings Account'); /* * Create splitted expense of 66,- @@ -62,154 +92,156 @@ class SplitDataSeeder extends Seeder $today = new Carbon; $today->subDays(6); - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 1, // withdrawal - 'transaction_currency_id' => 1, - 'description' => 'Split Expense (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] - ); - - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. - $destinations = ['Albert Heijn', 'PLUS', 'Apple']; - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - $source = TestData::findAccount($user, 'TestData Checking Account'); - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( + if (!$skipWithdrawal) { + $journal = TransactionJournal::create( [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-22', - + 'user_id' => $user->id, + 'transaction_type_id' => 1, // withdrawal + 'transaction_currency_id' => 1, + 'description' => 'Split Expense (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), ] ); - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '22', + // split in 6 transactions (multiple destinations). 22,- each + // source is TestData Checking Account. + // also attach some budgets and stuff. + $destinations = ['Albert Heijn', 'PLUS', 'Apple']; + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + $source = TestData::findAccount($user, 'Checking Account'); + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); - ] - ); + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-22', - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); + ] + ); - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '22', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } } - // create splitted income of 99,- $today->addDay(); - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 2, // expense - 'transaction_currency_id' => 1, - 'description' => 'Split Income (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] - ); - - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. - $destinations = ['TestData Checking Account', 'TestData Savings', 'TestData Shared']; - $source = TestData::findAccount($user, 'Belastingdienst'); - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( + if (!$skipDeposit) { + $journal = TransactionJournal::create( [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-33', - + 'user_id' => $user->id, + 'transaction_type_id' => 2, // expense + 'transaction_currency_id' => 1, + 'description' => 'Split Income (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), ] ); - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '33', + // split in 6 transactions (multiple destinations). 22,- each + // source is TestData Checking Account. + // also attach some budgets and stuff. + $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; + $source = TestData::findAccount($user, 'Belastingdienst'); + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); - ] - ); + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-33', - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); + ] + ); - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '33', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } } - // create a splitted transfer of 57,- (19) $today->addDay(); - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 3, // transfer - 'transaction_currency_id' => 1, - 'description' => 'Split Transfer (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] - ); - - - $source = TestData::findAccount($user, 'Emergencies'); - $destinations = ['TestData Checking Account', 'TestData Savings', 'TestData Shared']; - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( + if (!$skipTransfer) { + $journal = TransactionJournal::create( [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-19', - + 'user_id' => $user->id, + 'transaction_type_id' => 3, // transfer + 'transaction_currency_id' => 1, + 'description' => 'Split Transfer (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), ] ); - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '19', - ] - ); + $source = TestData::findAccount($user, 'Alternate Checking Account'); + $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-19', - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); + ] + ); + + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '19', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } } - - } } diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index 0741f0473b..b58b090c47 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -46,11 +46,11 @@ class TestDataSeeder extends Seeder $user = TestData::createUsers(); // create all kinds of static data: - TestData::createAssetAccounts($user); + TestData::createAssetAccounts($user, []); TestData::createBills($user); TestData::createBudgets($user); TestData::createCategories($user); - TestData::createPiggybanks($user); + TestData::createPiggybanks($user, 'TestData Savings'); TestData::createExpenseAccounts($user); TestData::createRevenueAccounts($user); TestData::createAttachments($user, $this->start); From 3c1ff4d21f50b1f6683df49a412c02c13ab2c439 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 22:05:58 +0200 Subject: [PATCH 065/206] Fix query. --- app/Repositories/Budget/BudgetRepository.php | 11 ++++++- .../Shared/ComponentRepository.php | 31 +++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index ef6606d34c..1e90caf829 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -683,8 +683,17 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn } ) ->whereIn('transactions.account_id', $ids) + ->having('transaction_count', '=', 1) ->transactionTypes([TransactionType::WITHDRAWAL]) - ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); + ->first( + [ + DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), + DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), + ] + ); + if (is_null($entry)) { + return '0'; + } if (is_null($entry->journalAmount)) { return '0'; } diff --git a/app/Repositories/Shared/ComponentRepository.php b/app/Repositories/Shared/ComponentRepository.php index fff108104c..683121338b 100644 --- a/app/Repositories/Shared/ComponentRepository.php +++ b/app/Repositories/Shared/ComponentRepository.php @@ -26,19 +26,38 @@ class ComponentRepository */ protected function commonBalanceInPeriod($object, Carbon $start, Carbon $end, Collection $accounts) { - $ids = $accounts->pluck('id')->toArray(); - - + // all balances based on transaction journals: + // TODO somehow exclude those with transactions below? + // TODO needs a completely new query. + $ids = $accounts->pluck('id')->toArray(); $entry = $object->transactionjournals() - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) - ->before($end) ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->whereIn('accounts.id', $ids) + ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) + ->before($end) ->after($start) ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); $amount = $entry->journalAmount ?? '0'; - return $amount; + // all balances based on individual transactions (at the moment, it's an "or or"): + $entry = $object + ->transactions() + // left join journals to get some meta-information. + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + // also left join transaction types so we can do the same type of filtering. + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + // need to do these manually. + ->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) + ->whereIn('transactions.account_id', $ids) + ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); + + // sum of amount: + $extraAmount = $entry->journalAmount ?? '0'; + $result = bcadd($amount, $extraAmount); + + return $result; } } From 80350f84239276a0a6f00117ac5f2112c0bf3cad Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Apr 2016 22:30:11 +0200 Subject: [PATCH 066/206] Fix for transactions. --- .../Controllers/Chart/CategoryController.php | 112 +++++++++--------- .../Category/CategoryRepository.php | 3 + 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 892464b254..1298c0ea0f 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -270,6 +270,62 @@ class CategoryController extends Controller } + /** + * @param Category $category + * @param string $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return \Illuminate\Http\JsonResponse + */ + public function period(Category $category, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($reportType); + $cache->addProperty($accounts); + $cache->addProperty($category->id); + $cache->addProperty('category'); + $cache->addProperty('period'); + if ($cache->has()) { + return Response::json($cache->get()); + } + + /** @var SingleCategoryRepositoryInterface $repository */ + $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface'); + // loop over period, add by users range: + $current = clone $start; + $viewRange = Preferences::get('viewRange', '1M')->data; + $format = strval(trans('config.month')); + $set = new Collection; + while ($current < $end) { + $currentStart = clone $current; + $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); + + $spent = strval(array_sum($repository->spentPerDay($category, $currentStart, $currentEnd, $accounts))); + $earned = strval(array_sum($repository->earnedPerDay($category, $currentStart, $currentEnd, $accounts))); + + $entry = [ + $category->name, + $currentStart->formatLocalized($format), + $spent, + $earned, + + ]; + $set->push($entry); + $currentEnd->addDay(); + $current = clone $currentEnd; + } + $data = $this->generator->period($set); + $cache->store($data); + + return Response::json($data); + + } + /** * @param SCRI $repository * @param Category $category @@ -435,60 +491,4 @@ class CategoryController extends Controller return $data; } - /** - * @param Category $category - * @param string $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return \Illuminate\Http\JsonResponse - */ - public function period(Category $category, string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - $cache->addProperty($category->id); - $cache->addProperty('category'); - $cache->addProperty('period'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface'); - // loop over period, add by users range: - $current = clone $start; - $viewRange = Preferences::get('viewRange', '1M')->data; - $format = strval(trans('config.month')); - $set = new Collection; - while ($current < $end) { - $currentStart = clone $current; - $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); - - $spent = strval(array_sum($repository->spentPerDay($category, $currentStart, $currentEnd, $accounts))); - $earned = strval(array_sum($repository->earnedPerDay($category, $currentStart, $currentEnd, $accounts))); - - $entry = [ - $category->name, - $currentStart->formatLocalized($format), - $spent, - $earned, - - ]; - $set->push($entry); - $currentEnd->addDay(); - $current = clone $currentEnd; - } - $data = $this->generator->period($set); - $cache->store($data); - - return Response::json($data); - - } - } diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 0575a2b665..0b8209b76d 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -284,7 +284,9 @@ class CategoryRepository implements CategoryRepositoryInterface ->before($end) ->after($start) ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->having('transaction_count', '=', 1) ->transactionTypes($types); + if (count($accountIds) > 0) { $query->whereIn('transactions.account_id', $accountIds); } @@ -293,6 +295,7 @@ class CategoryRepository implements CategoryRepositoryInterface $single = $query->first( [ DB::raw('SUM(`transactions`.`amount`) as `sum`'), + DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), ] ); if (!is_null($single)) { From 77c9e3758456671892c3ad7e2f0d96c46cc76c75 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 1 May 2016 06:37:47 +0200 Subject: [PATCH 067/206] Move some stuff around. --- .../CrudController.php} | 217 ++-------------- .../Transaction/TransactionController.php | 237 ++++++++++++++++++ app/Http/routes.php | 26 +- 3 files changed, 266 insertions(+), 214 deletions(-) rename app/Http/Controllers/{TransactionController.php => Transaction/CrudController.php} (62%) create mode 100644 app/Http/Controllers/Transaction/TransactionController.php diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/Transaction/CrudController.php similarity index 62% rename from app/Http/Controllers/TransactionController.php rename to app/Http/Controllers/Transaction/CrudController.php index 74a35cc1b9..987c810cd5 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/Transaction/CrudController.php @@ -1,15 +1,21 @@ -data; - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = trans('firefly.title_' . $what); - $page = intval(Input::get('page')); - $journals = $repository->getJournalsOfTypes($types, $page, $pageSize); - - $journals->setPath('transactions/' . $what); - - return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals')); - - } - - /** - * @param Collection $journals - * - * @return View - */ - public function massDelete(Collection $journals) - { - $subTitle = trans('firefly.mass_delete_journals'); - - // put previous url in session - Session::put('transactions.mass-delete.url', URL::previous()); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'mass-delete'); - - return view('transactions.mass-delete', compact('journals', 'subTitle')); - - } - - /** - * @param MassDeleteJournalRequest $request - * @param JournalRepositoryInterface $repository - * - * @return mixed - */ - public function massDestroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository) - { - $ids = $request->get('confirm_mass_delete'); - $set = new Collection; - if (is_array($ids)) { - /** @var int $journalId */ - foreach ($ids as $journalId) { - /** @var TransactionJournal $journal */ - $journal = $repository->find($journalId); - if (!is_null($journal->id) && $journalId == $journal->id) { - $set->push($journal); - } - } - } - unset($journal); - $count = 0; - - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $repository->delete($journal); - $count++; - } - - Preferences::mark(); - Session::flash('success', trans('firefly.mass_deleted_transactions_success', ['amount' => $count])); - - // redirect to previous URL: - return redirect(session('transactions.mass-delete.url')); - - } - - /** - * @param Collection $journals - */ - public function massEdit(Collection $journals) - { - $subTitle = trans('firefly.mass_edit_journals'); - /** @var ARI $accountRepository */ - $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); - $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); - - // put previous url in session - Session::put('transactions.mass-edit.url', URL::previous()); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'mass-edit'); - - return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList')); - } - - /** - * - */ - public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository) - { - $journalIds = Input::get('journals'); - $count = 0; - if (is_array($journalIds)) { - foreach ($journalIds as $journalId) { - $journal = $repository->find(intval($journalId)); - if ($journal) { - // do update. - - // get optional fields: - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); - $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0; - $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0; - $expenseAccount = $request->get('expense_account')[$journal->id] ?? ''; - $revenueAccount = $request->get('revenue_account')[$journal->id] ?? ''; - $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0; - $category = $journal->categories->first() ? $journal->categories->first()->name : ''; - $tags = $journal->tags->pluck('tag')->toArray(); - - // for a deposit, the 'account_id' is the account the money is deposited on. - // needs a better way of handling. - // more uniform source/destination field names - $accountId = $sourceAccountId; - if ($what == 'deposit') { - $accountId = $destAccountId; - } - - // build data array - $data = [ - 'id' => $journal->id, - 'what' => $what, - 'description' => $request->get('description')[$journal->id], - 'account_id' => intval($accountId), - 'account_from_id' => intval($sourceAccountId), - 'account_to_id' => intval($destAccountId), - 'expense_account' => $expenseAccount, - 'revenue_account' => $revenueAccount, - 'amount' => round($request->get('amount')[$journal->id], 4), - 'user' => Auth::user()->id, - 'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)), - 'date' => new Carbon($request->get('date')[$journal->id]), - 'interest_date' => $journal->interest_date, - 'book_date' => $journal->book_date, - 'process_date' => $journal->process_date, - 'budget_id' => $budgetId, - 'category' => $category, - 'tags' => $tags, - - ]; - // call repository update function. - $repository->update($journal, $data); - - $count++; - } - } - } - Preferences::mark(); - Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count])); - - // redirect to previous URL: - return redirect(session('transactions.mass-edit.url')); - - } - - /** - * @param JournalRepositoryInterface $repository - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function reorder(JournalRepositoryInterface $repository) - { - $ids = Input::get('items'); - $date = new Carbon(Input::get('date')); - if (count($ids) > 0) { - $order = 0; - foreach ($ids as $id) { - - $journal = $repository->getWithDate($id, $date); - if ($journal) { - $journal->order = $order; - $order++; - $journal->save(); - } - } - } - Preferences::mark(); - - return Response::json([true]); - - } - /** * @param JournalRepositoryInterface $repository * @param TransactionJournal $journal @@ -468,7 +285,6 @@ class TransactionController extends Controller } - /** * @param JournalFormRequest $request * @param JournalRepositoryInterface $repository @@ -512,5 +328,4 @@ class TransactionController extends Controller return redirect(session('transactions.edit.url')); } - -} +} \ No newline at end of file diff --git a/app/Http/Controllers/Transaction/TransactionController.php b/app/Http/Controllers/Transaction/TransactionController.php new file mode 100644 index 0000000000..4aad76e3c6 --- /dev/null +++ b/app/Http/Controllers/Transaction/TransactionController.php @@ -0,0 +1,237 @@ +data; + $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); + $types = config('firefly.transactionTypesByWhat.' . $what); + $subTitle = trans('firefly.title_' . $what); + $page = intval(Input::get('page')); + $journals = $repository->getJournalsOfTypes($types, $page, $pageSize); + + $journals->setPath('transactions/' . $what); + + return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals')); + + } + + /** + * @param Collection $journals + * + * @return View + */ + public function massDelete(Collection $journals) + { + $subTitle = trans('firefly.mass_delete_journals'); + + // put previous url in session + Session::put('transactions.mass-delete.url', URL::previous()); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'mass-delete'); + + return view('transactions.mass-delete', compact('journals', 'subTitle')); + + } + + /** + * @param MassDeleteJournalRequest $request + * @param JournalRepositoryInterface $repository + * + * @return mixed + */ + public function massDestroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository) + { + $ids = $request->get('confirm_mass_delete'); + $set = new Collection; + if (is_array($ids)) { + /** @var int $journalId */ + foreach ($ids as $journalId) { + /** @var TransactionJournal $journal */ + $journal = $repository->find($journalId); + if (!is_null($journal->id) && $journalId == $journal->id) { + $set->push($journal); + } + } + } + unset($journal); + $count = 0; + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $repository->delete($journal); + $count++; + } + + Preferences::mark(); + Session::flash('success', trans('firefly.mass_deleted_transactions_success', ['amount' => $count])); + + // redirect to previous URL: + return redirect(session('transactions.mass-delete.url')); + + } + + /** + * @param Collection $journals + */ + public function massEdit(Collection $journals) + { + $subTitle = trans('firefly.mass_edit_journals'); + /** @var ARI $accountRepository */ + $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + + // put previous url in session + Session::put('transactions.mass-edit.url', URL::previous()); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'mass-edit'); + + return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList')); + } + + /** + * + */ + public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository) + { + $journalIds = Input::get('journals'); + $count = 0; + if (is_array($journalIds)) { + foreach ($journalIds as $journalId) { + $journal = $repository->find(intval($journalId)); + if ($journal) { + // do update. + + // get optional fields: + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0; + $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0; + $expenseAccount = $request->get('expense_account')[$journal->id] ?? ''; + $revenueAccount = $request->get('revenue_account')[$journal->id] ?? ''; + $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0; + $category = $journal->categories->first() ? $journal->categories->first()->name : ''; + $tags = $journal->tags->pluck('tag')->toArray(); + + // for a deposit, the 'account_id' is the account the money is deposited on. + // needs a better way of handling. + // more uniform source/destination field names + $accountId = $sourceAccountId; + if ($what == 'deposit') { + $accountId = $destAccountId; + } + + // build data array + $data = [ + 'id' => $journal->id, + 'what' => $what, + 'description' => $request->get('description')[$journal->id], + 'account_id' => intval($accountId), + 'account_from_id' => intval($sourceAccountId), + 'account_to_id' => intval($destAccountId), + 'expense_account' => $expenseAccount, + 'revenue_account' => $revenueAccount, + 'amount' => round($request->get('amount')[$journal->id], 4), + 'user' => Auth::user()->id, + 'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)), + 'date' => new Carbon($request->get('date')[$journal->id]), + 'interest_date' => $journal->interest_date, + 'book_date' => $journal->book_date, + 'process_date' => $journal->process_date, + 'budget_id' => $budgetId, + 'category' => $category, + 'tags' => $tags, + + ]; + // call repository update function. + $repository->update($journal, $data); + + $count++; + } + } + } + Preferences::mark(); + Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count])); + + // redirect to previous URL: + return redirect(session('transactions.mass-edit.url')); + + } + + /** + * @param JournalRepositoryInterface $repository + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function reorder(JournalRepositoryInterface $repository) + { + $ids = Input::get('items'); + $date = new Carbon(Input::get('date')); + if (count($ids) > 0) { + $order = 0; + foreach ($ids as $id) { + + $journal = $repository->getWithDate($id, $date); + if ($journal) { + $journal->order = $order; + $order++; + $journal->save(); + } + } + } + Preferences::mark(); + + return Response::json([true]); + + } + + +} diff --git a/app/Http/routes.php b/app/Http/routes.php index d811da5f7f..1c87892279 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -367,28 +367,28 @@ Route::group( /** * Transaction Controller */ - Route::get('/transactions/{what}', ['uses' => 'TransactionController@index', 'as' => 'transactions.index'])->where( + Route::get('/transactions/{what}', ['uses' => 'Transaction\TransactionController@index', 'as' => 'transactions.index'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); - Route::get('/transactions/create/{what}', ['uses' => 'TransactionController@create', 'as' => 'transactions.create'])->where( + Route::get('/transactions/create/{what}', ['uses' => 'Transaction\CrudController@create', 'as' => 'transactions.create'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); - Route::get('/transaction/edit/{tj}', ['uses' => 'TransactionController@edit', 'as' => 'transactions.edit']); - Route::get('/transaction/delete/{tj}', ['uses' => 'TransactionController@delete', 'as' => 'transactions.delete']); - Route::get('/transaction/show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'transactions.show']); + Route::get('/transaction/edit/{tj}', ['uses' => 'Transaction\CrudController@edit', 'as' => 'transactions.edit']); + Route::get('/transaction/delete/{tj}', ['uses' => 'Transaction\CrudController@delete', 'as' => 'transactions.delete']); + Route::get('/transaction/show/{tj}', ['uses' => 'Transaction\CrudController@show', 'as' => 'transactions.show']); // transaction controller: - Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])->where( + Route::post('/transactions/store/{what}', ['uses' => 'Transaction\CrudController@store', 'as' => 'transactions.store'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); - Route::post('/transaction/update/{tj}', ['uses' => 'TransactionController@update', 'as' => 'transactions.update']); - Route::post('/transaction/destroy/{tj}', ['uses' => 'TransactionController@destroy', 'as' => 'transactions.destroy']); - Route::post('/transaction/reorder', ['uses' => 'TransactionController@reorder', 'as' => 'transactions.reorder']); + Route::post('/transaction/update/{tj}', ['uses' => 'Transaction\CrudController@update', 'as' => 'transactions.update']); + Route::post('/transaction/destroy/{tj}', ['uses' => 'Transaction\CrudController@destroy', 'as' => 'transactions.destroy']); + Route::post('/transaction/reorder', ['uses' => 'Transaction\TransactionController@reorder', 'as' => 'transactions.reorder']); // mass edit and mass delete. - Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'TransactionController@massEdit', 'as' => 'transactions.mass-edit']); - Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'TransactionController@massDelete', 'as' => 'transactions.mass-delete']); - Route::post('/transactions/mass-update', ['uses' => 'TransactionController@massUpdate', 'as' => 'transactions.mass-update']); - Route::post('/transactions/mass-destroy', ['uses' => 'TransactionController@massDestroy', 'as' => 'transactions.mass-destroy']); + Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'Transaction\TransactionController@massEdit', 'as' => 'transactions.mass-edit']); + Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'Transaction\TransactionController@massDelete', 'as' => 'transactions.mass-delete']); + Route::post('/transactions/mass-update', ['uses' => 'Transaction\TransactionController@massUpdate', 'as' => 'transactions.mass-update']); + Route::post('/transactions/mass-destroy', ['uses' => 'Transaction\TransactionController@massDestroy', 'as' => 'transactions.mass-destroy']); /** * POPUP Controllers From bfa7ee90f451417f27f01ba9f58f2fb89ec9e4c8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 1 May 2016 06:59:08 +0200 Subject: [PATCH 068/206] Reverse stuff --- .../Transaction/TransactionController.php | 237 ------------------ ...ntroller.php => TransactionController.php} | 223 +++++++++++++++- app/Http/routes.php | 26 +- 3 files changed, 223 insertions(+), 263 deletions(-) delete mode 100644 app/Http/Controllers/Transaction/TransactionController.php rename app/Http/Controllers/{Transaction/CrudController.php => TransactionController.php} (61%) diff --git a/app/Http/Controllers/Transaction/TransactionController.php b/app/Http/Controllers/Transaction/TransactionController.php deleted file mode 100644 index 4aad76e3c6..0000000000 --- a/app/Http/Controllers/Transaction/TransactionController.php +++ /dev/null @@ -1,237 +0,0 @@ -data; - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = trans('firefly.title_' . $what); - $page = intval(Input::get('page')); - $journals = $repository->getJournalsOfTypes($types, $page, $pageSize); - - $journals->setPath('transactions/' . $what); - - return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals')); - - } - - /** - * @param Collection $journals - * - * @return View - */ - public function massDelete(Collection $journals) - { - $subTitle = trans('firefly.mass_delete_journals'); - - // put previous url in session - Session::put('transactions.mass-delete.url', URL::previous()); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'mass-delete'); - - return view('transactions.mass-delete', compact('journals', 'subTitle')); - - } - - /** - * @param MassDeleteJournalRequest $request - * @param JournalRepositoryInterface $repository - * - * @return mixed - */ - public function massDestroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository) - { - $ids = $request->get('confirm_mass_delete'); - $set = new Collection; - if (is_array($ids)) { - /** @var int $journalId */ - foreach ($ids as $journalId) { - /** @var TransactionJournal $journal */ - $journal = $repository->find($journalId); - if (!is_null($journal->id) && $journalId == $journal->id) { - $set->push($journal); - } - } - } - unset($journal); - $count = 0; - - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $repository->delete($journal); - $count++; - } - - Preferences::mark(); - Session::flash('success', trans('firefly.mass_deleted_transactions_success', ['amount' => $count])); - - // redirect to previous URL: - return redirect(session('transactions.mass-delete.url')); - - } - - /** - * @param Collection $journals - */ - public function massEdit(Collection $journals) - { - $subTitle = trans('firefly.mass_edit_journals'); - /** @var ARI $accountRepository */ - $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); - $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); - - // put previous url in session - Session::put('transactions.mass-edit.url', URL::previous()); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'mass-edit'); - - return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList')); - } - - /** - * - */ - public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository) - { - $journalIds = Input::get('journals'); - $count = 0; - if (is_array($journalIds)) { - foreach ($journalIds as $journalId) { - $journal = $repository->find(intval($journalId)); - if ($journal) { - // do update. - - // get optional fields: - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); - $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0; - $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0; - $expenseAccount = $request->get('expense_account')[$journal->id] ?? ''; - $revenueAccount = $request->get('revenue_account')[$journal->id] ?? ''; - $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0; - $category = $journal->categories->first() ? $journal->categories->first()->name : ''; - $tags = $journal->tags->pluck('tag')->toArray(); - - // for a deposit, the 'account_id' is the account the money is deposited on. - // needs a better way of handling. - // more uniform source/destination field names - $accountId = $sourceAccountId; - if ($what == 'deposit') { - $accountId = $destAccountId; - } - - // build data array - $data = [ - 'id' => $journal->id, - 'what' => $what, - 'description' => $request->get('description')[$journal->id], - 'account_id' => intval($accountId), - 'account_from_id' => intval($sourceAccountId), - 'account_to_id' => intval($destAccountId), - 'expense_account' => $expenseAccount, - 'revenue_account' => $revenueAccount, - 'amount' => round($request->get('amount')[$journal->id], 4), - 'user' => Auth::user()->id, - 'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)), - 'date' => new Carbon($request->get('date')[$journal->id]), - 'interest_date' => $journal->interest_date, - 'book_date' => $journal->book_date, - 'process_date' => $journal->process_date, - 'budget_id' => $budgetId, - 'category' => $category, - 'tags' => $tags, - - ]; - // call repository update function. - $repository->update($journal, $data); - - $count++; - } - } - } - Preferences::mark(); - Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count])); - - // redirect to previous URL: - return redirect(session('transactions.mass-edit.url')); - - } - - /** - * @param JournalRepositoryInterface $repository - * - * @return \Symfony\Component\HttpFoundation\Response - */ - public function reorder(JournalRepositoryInterface $repository) - { - $ids = Input::get('items'); - $date = new Carbon(Input::get('date')); - if (count($ids) > 0) { - $order = 0; - foreach ($ids as $id) { - - $journal = $repository->getWithDate($id, $date); - if ($journal) { - $journal->order = $order; - $order++; - $journal->save(); - } - } - } - Preferences::mark(); - - return Response::json([true]); - - } - - -} diff --git a/app/Http/Controllers/Transaction/CrudController.php b/app/Http/Controllers/TransactionController.php similarity index 61% rename from app/Http/Controllers/Transaction/CrudController.php rename to app/Http/Controllers/TransactionController.php index 987c810cd5..b4b62b445d 100644 --- a/app/Http/Controllers/Transaction/CrudController.php +++ b/app/Http/Controllers/TransactionController.php @@ -1,21 +1,24 @@ getAccounts(['Default account', 'Asset account'])); $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); @@ -201,6 +203,201 @@ class CrudController extends Controller ); } + /** + * @param JournalRepositoryInterface $repository + * @param $what + * + * @return \Illuminate\View\View + */ + public function index(JournalRepositoryInterface $repository, string $what) + { + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); + $types = config('firefly.transactionTypesByWhat.' . $what); + $subTitle = trans('firefly.title_' . $what); + $page = intval(Input::get('page')); + $journals = $repository->getJournalsOfTypes($types, $page, $pageSize); + + $journals->setPath('transactions/' . $what); + + return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals')); + + } + + /** + * @param Collection $journals + * + * @return View + */ + public function massDelete(Collection $journals) + { + $subTitle = trans('firefly.mass_delete_journals'); + + // put previous url in session + Session::put('transactions.mass-delete.url', URL::previous()); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'mass-delete'); + + return view('transactions.mass-delete', compact('journals', 'subTitle')); + + } + + /** + * @param MassDeleteJournalRequest $request + * @param JournalRepositoryInterface $repository + * + * @return mixed + */ + public function massDestroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository) + { + $ids = $request->get('confirm_mass_delete'); + $set = new Collection; + if (is_array($ids)) { + /** @var int $journalId */ + foreach ($ids as $journalId) { + /** @var TransactionJournal $journal */ + $journal = $repository->find($journalId); + if (!is_null($journal->id) && $journalId == $journal->id) { + $set->push($journal); + } + } + } + unset($journal); + $count = 0; + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $repository->delete($journal); + $count++; + } + + Preferences::mark(); + Session::flash('success', trans('firefly.mass_deleted_transactions_success', ['amount' => $count])); + + // redirect to previous URL: + return redirect(session('transactions.mass-delete.url')); + + } + + + /** + * @param Collection $journals + * + * @return View + */ + public function massEdit(Collection $journals) + { + $subTitle = trans('firefly.mass_edit_journals'); + /** @var ARI $accountRepository */ + $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + + // put previous url in session + Session::put('transactions.mass-edit.url', URL::previous()); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'mass-edit'); + + return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList')); + } + + /** + * @param MassEditJournalRequest $request + * @param JournalRepositoryInterface $repository + * + * @return mixed + */ + public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository) + { + $journalIds = Input::get('journals'); + $count = 0; + if (is_array($journalIds)) { + foreach ($journalIds as $journalId) { + $journal = $repository->find(intval($journalId)); + if ($journal) { + // do update. + + // get optional fields: + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0; + $destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0; + $expenseAccount = $request->get('expense_account')[$journal->id] ?? ''; + $revenueAccount = $request->get('revenue_account')[$journal->id] ?? ''; + $budgetId = $journal->budgets->first() ? $journal->budgets->first()->id : 0; + $category = $journal->categories->first() ? $journal->categories->first()->name : ''; + $tags = $journal->tags->pluck('tag')->toArray(); + + // for a deposit, the 'account_id' is the account the money is deposited on. + // needs a better way of handling. + // more uniform source/destination field names + $accountId = $sourceAccountId; + if ($what == 'deposit') { + $accountId = $destAccountId; + } + + // build data array + $data = [ + 'id' => $journal->id, + 'what' => $what, + 'description' => $request->get('description')[$journal->id], + 'account_id' => intval($accountId), + 'account_from_id' => intval($sourceAccountId), + 'account_to_id' => intval($destAccountId), + 'expense_account' => $expenseAccount, + 'revenue_account' => $revenueAccount, + 'amount' => round($request->get('amount')[$journal->id], 4), + 'user' => Auth::user()->id, + 'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)), + 'date' => new Carbon($request->get('date')[$journal->id]), + 'interest_date' => $journal->interest_date, + 'book_date' => $journal->book_date, + 'process_date' => $journal->process_date, + 'budget_id' => $budgetId, + 'category' => $category, + 'tags' => $tags, + + ]; + // call repository update function. + $repository->update($journal, $data); + + $count++; + } + } + } + Preferences::mark(); + Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count])); + + // redirect to previous URL: + return redirect(session('transactions.mass-edit.url')); + + } + + /** + * @param JournalRepositoryInterface $repository + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function reorder(JournalRepositoryInterface $repository) + { + $ids = Input::get('items'); + $date = new Carbon(Input::get('date')); + if (count($ids) > 0) { + $order = 0; + foreach ($ids as $id) { + + $journal = $repository->getWithDate($id, $date); + if ($journal) { + $journal->order = $order; + $order++; + $journal->save(); + } + } + } + Preferences::mark(); + + return Response::json([true]); + + } + /** * @param JournalRepositoryInterface $repository * @param TransactionJournal $journal @@ -267,7 +464,6 @@ class CrudController extends Controller Session::flash('info', $att->getMessages()->get('attachments')); } - Log::debug('Triggered TransactionJournalStored with transaction journal #' . $journal->id . ' and piggy #' . intval($request->get('piggy_bank_id'))); event(new TransactionJournalStored($journal, intval($request->get('piggy_bank_id')))); Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); @@ -285,6 +481,7 @@ class CrudController extends Controller } + /** * @param JournalFormRequest $request * @param JournalRepositoryInterface $repository @@ -328,4 +525,4 @@ class CrudController extends Controller return redirect(session('transactions.edit.url')); } -} \ No newline at end of file +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 1c87892279..d811da5f7f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -367,28 +367,28 @@ Route::group( /** * Transaction Controller */ - Route::get('/transactions/{what}', ['uses' => 'Transaction\TransactionController@index', 'as' => 'transactions.index'])->where( + Route::get('/transactions/{what}', ['uses' => 'TransactionController@index', 'as' => 'transactions.index'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); - Route::get('/transactions/create/{what}', ['uses' => 'Transaction\CrudController@create', 'as' => 'transactions.create'])->where( + Route::get('/transactions/create/{what}', ['uses' => 'TransactionController@create', 'as' => 'transactions.create'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); - Route::get('/transaction/edit/{tj}', ['uses' => 'Transaction\CrudController@edit', 'as' => 'transactions.edit']); - Route::get('/transaction/delete/{tj}', ['uses' => 'Transaction\CrudController@delete', 'as' => 'transactions.delete']); - Route::get('/transaction/show/{tj}', ['uses' => 'Transaction\CrudController@show', 'as' => 'transactions.show']); + Route::get('/transaction/edit/{tj}', ['uses' => 'TransactionController@edit', 'as' => 'transactions.edit']); + Route::get('/transaction/delete/{tj}', ['uses' => 'TransactionController@delete', 'as' => 'transactions.delete']); + Route::get('/transaction/show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'transactions.show']); // transaction controller: - Route::post('/transactions/store/{what}', ['uses' => 'Transaction\CrudController@store', 'as' => 'transactions.store'])->where( + Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); - Route::post('/transaction/update/{tj}', ['uses' => 'Transaction\CrudController@update', 'as' => 'transactions.update']); - Route::post('/transaction/destroy/{tj}', ['uses' => 'Transaction\CrudController@destroy', 'as' => 'transactions.destroy']); - Route::post('/transaction/reorder', ['uses' => 'Transaction\TransactionController@reorder', 'as' => 'transactions.reorder']); + Route::post('/transaction/update/{tj}', ['uses' => 'TransactionController@update', 'as' => 'transactions.update']); + Route::post('/transaction/destroy/{tj}', ['uses' => 'TransactionController@destroy', 'as' => 'transactions.destroy']); + Route::post('/transaction/reorder', ['uses' => 'TransactionController@reorder', 'as' => 'transactions.reorder']); // mass edit and mass delete. - Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'Transaction\TransactionController@massEdit', 'as' => 'transactions.mass-edit']); - Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'Transaction\TransactionController@massDelete', 'as' => 'transactions.mass-delete']); - Route::post('/transactions/mass-update', ['uses' => 'Transaction\TransactionController@massUpdate', 'as' => 'transactions.mass-update']); - Route::post('/transactions/mass-destroy', ['uses' => 'Transaction\TransactionController@massDestroy', 'as' => 'transactions.mass-destroy']); + Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'TransactionController@massEdit', 'as' => 'transactions.mass-edit']); + Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'TransactionController@massDelete', 'as' => 'transactions.mass-delete']); + Route::post('/transactions/mass-update', ['uses' => 'TransactionController@massUpdate', 'as' => 'transactions.mass-update']); + Route::post('/transactions/mass-destroy', ['uses' => 'TransactionController@massDestroy', 'as' => 'transactions.mass-destroy']); /** * POPUP Controllers From ac8ff4e565db3c57e733c3afb19ea54fc4853189 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 1 May 2016 07:09:58 +0200 Subject: [PATCH 069/206] Clean up repository. --- .../ChartJsPiggyBankChartGenerator.php | 6 ++--- .../Controllers/Chart/PiggyBankController.php | 18 +++++++++++++-- .../PiggyBank/PiggyBankRepository.php | 15 ++----------- .../PiggyBankRepositoryInterface.php | 22 ++++++++++++------- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php index 086e208039..a269c84571 100644 --- a/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php +++ b/app/Generator/Chart/PiggyBank/ChartJsPiggyBankChartGenerator.php @@ -36,9 +36,9 @@ class ChartJsPiggyBankChartGenerator implements PiggyBankChartGeneratorInterface ], ]; $sum = '0'; - foreach ($set as $entry) { - $date = new Carbon($entry->date); - $sum = bcadd($sum, $entry->sum); + foreach ($set as $key => $value) { + $date = new Carbon($key); + $sum = bcadd($sum, $value); $data['labels'][] = $date->formatLocalized($format); $data['datasets'][0]['data'][] = round($sum, 2); } diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php index c0bcff2052..ac3430d345 100644 --- a/app/Http/Controllers/Chart/PiggyBankController.php +++ b/app/Http/Controllers/Chart/PiggyBankController.php @@ -5,8 +5,10 @@ namespace FireflyIII\Http\Controllers\Chart; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Support\CacheProperties; +use Illuminate\Support\Collection; use Response; @@ -49,8 +51,20 @@ class PiggyBankController extends Controller return Response::json($cache->get()); } - $set = $repository->getEventSummarySet($piggyBank); - $data = $this->generator->history($set); + $set = $repository->getEvents($piggyBank); + $set = $set->reverse(); + $collection = []; + /** @var PiggyBankEvent $entry */ + foreach ($set as $entry) { + $date = $entry->date->format('Y-m-d'); + $amount = $entry->amount; + if (isset($collection[$date])) { + $amount = bcadd($amount, $collection[$date]); + } + $collection[$date] = $amount; + } + + $data = $this->generator->history(new Collection($collection)); $cache->store($data); return Response::json($data); diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 6c8e356a85..555e49aadf 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -22,7 +22,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface private $user; /** - * BillRepository constructor. + * PiggyBankRepository constructor. * * @param User $user */ @@ -48,6 +48,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface * @param PiggyBank $piggyBank * * @return bool + * @throws \Exception */ public function destroy(PiggyBank $piggyBank): bool { @@ -56,18 +57,6 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return true; } - /** - * @param PiggyBank $piggyBank - * - * @return Collection - */ - public function getEventSummarySet(PiggyBank $piggyBank): Collection - { - $var = DB::table('piggy_bank_events')->where('piggy_bank_id', $piggyBank->id)->groupBy('date')->get(['date', DB::raw('SUM(`amount`) AS `sum`')]); - - return new Collection($var); - } - /** * @param PiggyBank $piggyBank * diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 206e4704b8..ba29d6f0bf 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -16,6 +16,8 @@ interface PiggyBankRepositoryInterface { /** + * Create a new event. + * * @param PiggyBank $piggyBank * @param string $amount * @@ -24,6 +26,8 @@ interface PiggyBankRepositoryInterface public function createEvent(PiggyBank $piggyBank, string $amount): PiggyBankEvent; /** + * Destroy piggy bank. + * * @param PiggyBank $piggyBank * * @return bool @@ -31,13 +35,8 @@ interface PiggyBankRepositoryInterface public function destroy(PiggyBank $piggyBank): bool; /** - * @param PiggyBank $piggyBank + * Get all events. * - * @return Collection - */ - public function getEventSummarySet(PiggyBank $piggyBank) : Collection; - - /** * @param PiggyBank $piggyBank * * @return Collection @@ -45,11 +44,15 @@ interface PiggyBankRepositoryInterface public function getEvents(PiggyBank $piggyBank) : Collection; /** + * Highest order of all piggy banks. + * * @return int */ public function getMaxOrder(): int; /** + * Return all piggy banks. + * * @return Collection */ public function getPiggyBanks() : Collection; @@ -62,8 +65,7 @@ interface PiggyBankRepositoryInterface public function reset(): bool; /** - * - * set id of piggy bank. + * Set specific piggy bank to specific order. * * @param int $piggyBankId * @param int $order @@ -74,6 +76,8 @@ interface PiggyBankRepositoryInterface /** + * Store new piggy bank. + * * @param array $data * * @return PiggyBank @@ -81,6 +85,8 @@ interface PiggyBankRepositoryInterface public function store(array $data): PiggyBank; /** + * Update existing piggy bank. + * * @param PiggyBank $piggyBank * @param array $data * From c66df3cb2c41e24c5675d4f5f8cfd9f16c620c96 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 1 May 2016 09:42:08 +0200 Subject: [PATCH 070/206] Code cleanup. --- app/Http/Controllers/JsonController.php | 6 +- .../Controllers/TransactionController.php | 6 +- app/Providers/EventServiceProvider.php | 10 - .../Journal/JournalRepository.php | 178 +++++++----------- .../Journal/JournalRepositoryInterface.php | 59 +----- app/Rules/TransactionMatcher.php | 7 +- 6 files changed, 87 insertions(+), 179 deletions(-) diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index f5a7a3104d..27e31877f0 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -275,9 +275,9 @@ class JsonController extends Controller public function transactionJournals(JournalRepositoryInterface $repository, $what) { $descriptions = []; - $dbType = $repository->getTransactionType($what); - - $journals = $repository->getJournalsOfType($dbType); + $type = config('firefly.transactionTypesByWhat.' . $what); + $types = [$type]; + $journals = $repository->getJournals($types, 1, 50); foreach ($journals as $j) { $descriptions[] = $j->description; } diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index b4b62b445d..2883837f27 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -216,7 +216,7 @@ class TransactionController extends Controller $types = config('firefly.transactionTypesByWhat.' . $what); $subTitle = trans('firefly.title_' . $what); $page = intval(Input::get('page')); - $journals = $repository->getJournalsOfTypes($types, $page, $pageSize); + $journals = $repository->getJournals($types, $page, $pageSize); $journals->setPath('transactions/' . $what); @@ -384,8 +384,8 @@ class TransactionController extends Controller $order = 0; foreach ($ids as $id) { - $journal = $repository->getWithDate($id, $date); - if ($journal) { + $journal = $repository->find($id); + if ($journal && $journal->date->format('Y-m-d') == $date->format('Y-m-d')) { $journal->order = $order; $order++; $journal->save(); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 6d2b746d1d..f0f7c63c12 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -99,16 +99,6 @@ class EventServiceProvider extends ServiceProvider */ protected function registerDeleteEvents() { - TransactionJournal::deleted( - function (TransactionJournal $journal) { - - /** @var Transaction $transaction */ - foreach ($journal->transactions()->get() as $transaction) { - $transaction->delete(); - } - } - ); - Account::deleted( function (Account $account) { diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 2f38596910..62e749e193 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -46,6 +46,11 @@ class JournalRepository implements JournalRepositoryInterface */ public function delete(TransactionJournal $journal): bool { + /** @var Transaction $transaction */ + foreach ($journal->transactions()->get() as $transaction) { + $transaction->delete(); + } + $journal->delete(); return true; @@ -82,6 +87,8 @@ class JournalRepository implements JournalRepositoryInterface } /** + * @deprecated + * * @param TransactionJournal $journal * @param Transaction $transaction * @@ -105,37 +112,6 @@ class JournalRepository implements JournalRepositoryInterface } - /** - * @param array $types - * @param int $offset - * @param int $count - * - * @return Collection - */ - public function getCollectionOfTypes(array $types, int $offset, int $count): Collection - { - $set = $this->user->transactionJournals() - ->expanded() - ->transactionTypes($types) - ->take($count)->offset($offset) - ->orderBy('date', 'DESC') - ->orderBy('order', 'ASC') - ->orderBy('id', 'DESC') - ->get(TransactionJournal::queryFields()); - - return $set; - } - - /** - * @param TransactionType $dbType - * - * @return Collection - */ - public function getJournalsOfType(TransactionType $dbType): Collection - { - return $this->user->transactionjournals()->where('transaction_type_id', $dbType->id)->orderBy('id', 'DESC')->take(50)->get(); - } - /** * @param array $types * @param int $page @@ -143,14 +119,13 @@ class JournalRepository implements JournalRepositoryInterface * * @return LengthAwarePaginator */ - public function getJournalsOfTypes(array $types, int $page, int $pageSize = 50): LengthAwarePaginator + public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator { $offset = ($page - 1) * $pageSize; - $query = $this->user - ->transactionJournals() - ->expanded() - ->transactionTypes($types); - + $query = $this->user->transactionJournals()->expanded(); + if (count($types) > 0) { + $query->transactionTypes($types); + } $count = $query->count(); $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); @@ -159,54 +134,6 @@ class JournalRepository implements JournalRepositoryInterface return $journals; } - /** - * @param string $type - * - * @return TransactionType - */ - public function getTransactionType(string $type): TransactionType - { - return TransactionType::whereType($type)->first(); - } - - /** - * @param int $journalId - * @param Carbon $date - * - * @return TransactionJournal - */ - public function getWithDate(int $journalId, Carbon $date): TransactionJournal - { - return $this->user->transactionjournals()->where('id', $journalId)->where('date', $date->format('Y-m-d 00:00:00'))->first(); - } - - /** - * - * * Remember: a balancingAct takes at most one expense and one transfer. - * an advancePayment takes at most one expense, infinite deposits and NO transfers. - * - * @param TransactionJournal $journal - * @param array $array - * - * @return bool - */ - public function saveTags(TransactionJournal $journal, array $array): bool - { - /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */ - $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); - - foreach ($array as $name) { - if (strlen(trim($name)) > 0) { - $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); - if (!is_null($tag)) { - $tagRepository->connect($journal, $tag); - } - } - } - - return true; - } - /** * @param array $data * @@ -340,44 +267,29 @@ class JournalRepository implements JournalRepositoryInterface } /** + * + * * Remember: a balancingAct takes at most one expense and one transfer. + * an advancePayment takes at most one expense, infinite deposits and NO transfers. + * * @param TransactionJournal $journal * @param array $array * * @return bool */ - public function updateTags(TransactionJournal $journal, array $array): bool + private function saveTags(TransactionJournal $journal, array $array): bool { - // create tag repository /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */ $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); - - // find or create all tags: - $tags = []; - $ids = []; foreach ($array as $name) { if (strlen(trim($name)) > 0) { - $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); - $tags[] = $tag; - $ids[] = $tag->id; + $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); + if (!is_null($tag)) { + $tagRepository->connect($journal, $tag); + } } } - // delete all tags connected to journal not in this array: - if (count($ids) > 0) { - DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete(); - } - // if count is zero, delete them all: - if (count($ids) == 0) { - DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete(); - } - - // connect each tag to journal (if not yet connected): - /** @var Tag $tag */ - foreach ($tags as $tag) { - $tagRepository->connect($journal, $tag); - } - return true; } @@ -388,7 +300,7 @@ class JournalRepository implements JournalRepositoryInterface * @return array * @throws FireflyException */ - protected function storeAccounts(TransactionType $type, array $data): array + private function storeAccounts(TransactionType $type, array $data): array { $sourceAccount = null; $destinationAccount = null; @@ -429,7 +341,7 @@ class JournalRepository implements JournalRepositoryInterface * * @return array */ - protected function storeDepositAccounts(array $data): array + private function storeDepositAccounts(array $data): array { $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']); @@ -455,7 +367,7 @@ class JournalRepository implements JournalRepositoryInterface * * @return array */ - protected function storeWithdrawalAccounts(array $data): array + private function storeWithdrawalAccounts(array $data): array { $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); @@ -481,4 +393,46 @@ class JournalRepository implements JournalRepositoryInterface } + + /** + * @param TransactionJournal $journal + * @param array $array + * + * @return bool + */ + private function updateTags(TransactionJournal $journal, array $array): bool + { + // create tag repository + /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */ + $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); + + + // find or create all tags: + $tags = []; + $ids = []; + foreach ($array as $name) { + if (strlen(trim($name)) > 0) { + $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]); + $tags[] = $tag; + $ids[] = $tag->id; + } + } + + // delete all tags connected to journal not in this array: + if (count($ids) > 0) { + DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete(); + } + // if count is zero, delete them all: + if (count($ids) == 0) { + DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete(); + } + + // connect each tag to journal (if not yet connected): + /** @var Tag $tag */ + foreach ($tags as $tag) { + $tagRepository->connect($journal, $tag); + } + + return true; + } } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index ea4526806c..83ffc138cb 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -18,6 +18,8 @@ use Illuminate\Support\Collection; interface JournalRepositoryInterface { /** + * Deletes a journal. + * * @param TransactionJournal $journal * * @return bool @@ -25,6 +27,8 @@ interface JournalRepositoryInterface public function delete(TransactionJournal $journal): bool; /** + * Find a specific journal + * * @param int $journalId * * @return TransactionJournal @@ -32,13 +36,17 @@ interface JournalRepositoryInterface public function find(int $journalId) : TransactionJournal; /** - * Get users first transaction journal + * Get users very first transaction journal * * @return TransactionJournal */ public function first(): TransactionJournal; /** + * Returns the amount in the account before the specified transaction took place. + * + * @deprecated + * * @param TransactionJournal $journal * @param Transaction $transaction * @@ -46,22 +54,6 @@ interface JournalRepositoryInterface */ public function getAmountBefore(TransactionJournal $journal, Transaction $transaction): string; - /** - * @param array $types - * @param int $offset - * @param int $count - * - * @return Collection - */ - public function getCollectionOfTypes(array $types, int $offset, int $count):Collection; - - /** - * @param TransactionType $dbType - * - * @return Collection - */ - public function getJournalsOfType(TransactionType $dbType): Collection; - /** * @param array $types * @param int $page @@ -69,31 +61,7 @@ interface JournalRepositoryInterface * * @return LengthAwarePaginator */ - public function getJournalsOfTypes(array $types, int $page, int $pageSize = 50): LengthAwarePaginator; - - /** - * @param string $type - * - * @return TransactionType - */ - public function getTransactionType(string $type): TransactionType; - - /** - * @param int $journalId - * @param Carbon $date - * - * @return TransactionJournal - */ - public function getWithDate(int $journalId, Carbon $date): TransactionJournal; - - /** - * - * @param TransactionJournal $journal - * @param array $array - * - * @return bool - */ - public function saveTags(TransactionJournal $journal, array $array): bool; + public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator; /** * @param array $data @@ -110,11 +78,4 @@ interface JournalRepositoryInterface */ public function update(TransactionJournal $journal, array $data): TransactionJournal; - /** - * @param TransactionJournal $journal - * @param array $array - * - * @return bool - */ - public function updateTags(TransactionJournal $journal, array $array): bool; } diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php index 80be3c7366..d14911e212 100644 --- a/app/Rules/TransactionMatcher.php +++ b/app/Rules/TransactionMatcher.php @@ -72,8 +72,11 @@ class TransactionMatcher // - the maximum number of transactions to search in have been searched do { // Fetch a batch of transactions from the database - $offset = $page > 0 ? ($page - 1) * $pagesize : 0; - $set = $this->repository->getCollectionOfTypes($this->transactionTypes, $offset, $pagesize); + //$offset = $page > 0 ? ($page - 1) * $pagesize : 0; + //$set = $this->repository->getCollectionOfTypes($this->transactionTypes, $offset, $pagesize); + $paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize); + $set = $paginator->getCollection(); + // Filter transactions that match the given triggers. $filtered = $set->filter( From b80db054e28c7cf562e39666e0becaf35301850c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 1 May 2016 09:52:58 +0200 Subject: [PATCH 071/206] Remove journal collector. --- app/Export/Processor.php | 11 ++- ...ExecuteRuleGroupOnExistingTransactions.php | 7 +- app/Repositories/Journal/JournalCollector.php | 67 ------------------- .../Journal/JournalRepository.php | 31 +++++++++ .../Journal/JournalRepositoryInterface.php | 14 +++- 5 files changed, 53 insertions(+), 77 deletions(-) delete mode 100644 app/Repositories/Journal/JournalCollector.php diff --git a/app/Export/Processor.php b/app/Export/Processor.php index a9aeec89ee..e364ed1373 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -10,12 +10,11 @@ declare(strict_types = 1); namespace FireflyIII\Export; -use Auth; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Export\Entry\Entry; use FireflyIII\Models\ExportJob; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Journal\JournalCollector; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Collection; use Log; @@ -91,10 +90,10 @@ class Processor */ public function collectJournals(): bool { - $args = [$this->accounts, Auth::user(), $this->settings['startDate'], $this->settings['endDate']]; - /** @var JournalCollector $journalCollector */ - $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args); - $this->journals = $journalCollector->collect(); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $this->journals = $repository->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']); + Log::debug( 'Collected ' . $this->journals->count() . ' journals (between ' . diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php index 20b33230e9..8affcf84a9 100644 --- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -4,6 +4,7 @@ namespace FireflyIII\Jobs; use Carbon\Carbon; use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Rules\Processor; use FireflyIII\User; use Illuminate\Contracts\Queue\ShouldQueue; @@ -143,10 +144,10 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue */ protected function collectJournals() { - $args = [$this->accounts, $this->user, $this->startDate, $this->endDate]; - $journalCollector = app('FireflyIII\Repositories\Journal\JournalCollector', $args); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); - return $journalCollector->collect(); + return $repository->getJournalsInRange($this->accounts, $this->startDate, $this->endDate); } /** diff --git a/app/Repositories/Journal/JournalCollector.php b/app/Repositories/Journal/JournalCollector.php deleted file mode 100644 index 871563113b..0000000000 --- a/app/Repositories/Journal/JournalCollector.php +++ /dev/null @@ -1,67 +0,0 @@ -accounts = $accounts; - $this->user = $user; - $this->start = $start; - $this->end = $end; - } - - /** - * @deprecated - * @return Collection - */ - public function collect(): Collection - { - // get all the journals: - $ids = $this->accounts->pluck('id')->toArray(); - - return $this->user->transactionjournals() - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereIn('transactions.account_id', $ids) - ->before($this->end) - ->after($this->start) - ->orderBy('transaction_journals.date') - ->get(['transaction_journals.*']); - } - -} diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 62e749e193..09738bd6d3 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -15,6 +15,7 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Log; @@ -134,6 +135,36 @@ class JournalRepository implements JournalRepositoryInterface return $journals; } + /** + * Returns a collection of ALL journals, given a specific account and a date range. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection + { + $query = $this->user->transactionJournals()->expanded(); + $query->before($end); + $query->after($start); + + if ($accounts->count() > 0) { + $ids = $accounts->pluck('id')->toArray(); + $query->where( + function (Builder $q) use ($ids) { + $q->whereIn('destination.account_id', $ids); + $q->orWhereIn('source.account_id', $ids); + } + ); + } + + $set = $query->get(TransactionJournal::queryFields()); + + return $set; + } + /** * @param array $data * diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 83ffc138cb..a31c8cf076 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -6,7 +6,6 @@ namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -55,6 +54,8 @@ interface JournalRepositoryInterface public function getAmountBefore(TransactionJournal $journal, Transaction $transaction): string; /** + * Returns a page of a specific type(s) of journal. + * * @param array $types * @param int $page * @param int $pageSize @@ -63,6 +64,17 @@ interface JournalRepositoryInterface */ public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator; + /** + * Returns a collection of ALL journals, given a specific account and a date range. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection; + /** * @param array $data * From 5e1167b8aed3288687ce88daa5c622ab137f8c0c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 1 May 2016 15:05:29 +0200 Subject: [PATCH 072/206] Code cleanup. --- app/Console/Commands/VerifyDatabase.php | 4 ++-- app/Export/Collector/AttachmentCollector.php | 3 ++- app/Export/Processor.php | 10 +++++++--- app/Handlers/Events/AttachUserRole.php | 2 +- app/Helpers/Csv/Converter/AccountId.php | 2 +- app/Helpers/Csv/Converter/AssetAccountIban.php | 3 +-- app/Helpers/Csv/Converter/AssetAccountName.php | 2 +- app/Helpers/Csv/Converter/AssetAccountNumber.php | 2 +- app/Helpers/Csv/Converter/BillId.php | 2 +- app/Helpers/Csv/Converter/BillName.php | 2 +- app/Helpers/Csv/Converter/BudgetId.php | 2 +- app/Helpers/Csv/Converter/BudgetName.php | 2 +- app/Helpers/Csv/Converter/CategoryId.php | 2 +- app/Helpers/Csv/Converter/CategoryName.php | 2 +- app/Helpers/Csv/Converter/CurrencyCode.php | 2 +- app/Helpers/Csv/Converter/CurrencyId.php | 2 +- app/Helpers/Csv/Converter/CurrencyName.php | 2 +- app/Helpers/Csv/Converter/CurrencySymbol.php | 2 +- app/Helpers/Csv/Converter/OpposingAccountIban.php | 2 +- app/Helpers/Csv/Converter/OpposingAccountId.php | 2 +- app/Helpers/Csv/Converter/OpposingAccountName.php | 2 +- app/Helpers/Csv/Converter/TagsComma.php | 2 +- app/Helpers/Csv/Converter/TagsSpace.php | 2 +- app/Helpers/Csv/PostProcessing/AssetAccount.php | 5 +++-- app/Http/Controllers/Popup/ReportController.php | 6 +++--- app/Http/Controllers/ReportController.php | 2 +- app/Http/Controllers/TransactionController.php | 2 +- app/Repositories/Bill/BillRepository.php | 2 +- 28 files changed, 40 insertions(+), 35 deletions(-) diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 3b21b24cb3..78001f33f4 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -241,12 +241,12 @@ having transaction_count = 0 private function reportSum() { /** @var UserRepositoryInterface $userRepository */ - $userRepository = app('FireflyIII\Repositories\User\UserRepositoryInterface'); + $userRepository = app(UserRepositoryInterface::class); /** @var User $user */ foreach ($userRepository->all() as $user) { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface', [$user]); + $repository = app(AccountRepositoryInterface::class, [$user]); $sum = $repository->sumOfEverything(); if (bccomp($sum, '0') !== 0) { $this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!'); diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php index e3882d6a62..563d3ff532 100644 --- a/app/Export/Collector/AttachmentCollector.php +++ b/app/Export/Collector/AttachmentCollector.php @@ -44,7 +44,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface */ public function __construct(ExportJob $job) { - $this->repository = app('FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface'); + /** @var AttachmentRepositoryInterface repository */ + $this->repository = app(AttachmentRepositoryInterface::class); // make storage: $this->uploadDisk = Storage::disk('upload'); $this->exportDisk = Storage::disk('export'); diff --git a/app/Export/Processor.php b/app/Export/Processor.php index e364ed1373..3d3e3f2c48 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -11,6 +11,8 @@ declare(strict_types = 1); namespace FireflyIII\Export; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Export\Collector\AttachmentCollector; +use FireflyIII\Export\Collector\UploadCollector; use FireflyIII\Export\Entry\Entry; use FireflyIII\Models\ExportJob; use FireflyIII\Models\TransactionJournal; @@ -78,7 +80,8 @@ class Processor */ public function collectAttachments(): bool { - $attachmentCollector = app('FireflyIII\Export\Collector\AttachmentCollector', [$this->job]); + /** @var AttachmentCollector $attachmentCollector */ + $attachmentCollector = app(AttachmentCollector::class, [$this->job]); $attachmentCollector->run(); $this->files = $this->files->merge($attachmentCollector->getFiles()); @@ -110,7 +113,8 @@ class Processor */ public function collectOldUploads(): bool { - $uploadCollector = app('FireflyIII\Export\Collector\UploadCollector', [$this->job]); + /** @var UploadCollector $uploadCollector */ + $uploadCollector = app(UploadCollector::class, [$this->job]); $uploadCollector->run(); $this->files = $this->files->merge($uploadCollector->getFiles()); @@ -139,7 +143,7 @@ class Processor */ public function createConfigFile(): bool { - $this->configurationMaker = app('FireflyIII\Export\ConfigurationFile', [$this->job]); + $this->configurationMaker = app(ConfigurationFile::class, [$this->job]); $this->files->push($this->configurationMaker->make()); return true; diff --git a/app/Handlers/Events/AttachUserRole.php b/app/Handlers/Events/AttachUserRole.php index a063658c22..86a0d5d102 100644 --- a/app/Handlers/Events/AttachUserRole.php +++ b/app/Handlers/Events/AttachUserRole.php @@ -40,7 +40,7 @@ class AttachUserRole { Log::debug('Trigger attachuserrole'); /** @var UserRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\User\UserRepositoryInterface'); + $repository = app(UserRepositoryInterface::class); // first user ever? if ($repository->count() == 1) { diff --git a/app/Helpers/Csv/Converter/AccountId.php b/app/Helpers/Csv/Converter/AccountId.php index 0ec7072a17..c1ec021dee 100644 --- a/app/Helpers/Csv/Converter/AccountId.php +++ b/app/Helpers/Csv/Converter/AccountId.php @@ -19,7 +19,7 @@ class AccountId extends BasicConverter implements ConverterInterface public function convert(): Account { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); $var = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $account = $repository->find($var); diff --git a/app/Helpers/Csv/Converter/AssetAccountIban.php b/app/Helpers/Csv/Converter/AssetAccountIban.php index 83636c4de5..83c5dd2c34 100644 --- a/app/Helpers/Csv/Converter/AssetAccountIban.php +++ b/app/Helpers/Csv/Converter/AssetAccountIban.php @@ -22,7 +22,7 @@ class AssetAccountIban extends BasicConverter implements ConverterInterface public function convert(): Account { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); // is mapped? Then it's easy! if (isset($this->mapped[$this->index][$this->value])) { @@ -44,7 +44,6 @@ class AssetAccountIban extends BasicConverter implements ConverterInterface /** * @param AccountRepositoryInterface $repository - * @param $value * * @return Account */ diff --git a/app/Helpers/Csv/Converter/AssetAccountName.php b/app/Helpers/Csv/Converter/AssetAccountName.php index 8bec1cee8a..cfe81f487a 100644 --- a/app/Helpers/Csv/Converter/AssetAccountName.php +++ b/app/Helpers/Csv/Converter/AssetAccountName.php @@ -21,7 +21,7 @@ class AssetAccountName extends BasicConverter implements ConverterInterface public function convert(): Account { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { $account = $repository->find(intval($this->mapped[$this->index][$this->value])); diff --git a/app/Helpers/Csv/Converter/AssetAccountNumber.php b/app/Helpers/Csv/Converter/AssetAccountNumber.php index ece314361c..646504635f 100644 --- a/app/Helpers/Csv/Converter/AssetAccountNumber.php +++ b/app/Helpers/Csv/Converter/AssetAccountNumber.php @@ -30,7 +30,7 @@ class AssetAccountNumber extends BasicConverter implements ConverterInterface public function convert(): Account { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); // is mapped? Then it's easy! if (isset($this->mapped[$this->index][$this->value])) { diff --git a/app/Helpers/Csv/Converter/BillId.php b/app/Helpers/Csv/Converter/BillId.php index 847ead3b90..30573e8c37 100644 --- a/app/Helpers/Csv/Converter/BillId.php +++ b/app/Helpers/Csv/Converter/BillId.php @@ -19,7 +19,7 @@ class BillId extends BasicConverter implements ConverterInterface public function convert(): Bill { /** @var BillRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository = app(BillRepositoryInterface::class); $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $bill = $repository->find($value); diff --git a/app/Helpers/Csv/Converter/BillName.php b/app/Helpers/Csv/Converter/BillName.php index 11de5bdbb2..93edca01cd 100644 --- a/app/Helpers/Csv/Converter/BillName.php +++ b/app/Helpers/Csv/Converter/BillName.php @@ -19,7 +19,7 @@ class BillName extends BasicConverter implements ConverterInterface public function convert(): Bill { /** @var BillRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository = app(BillRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { return $repository->find($this->mapped[$this->index][$this->value]); diff --git a/app/Helpers/Csv/Converter/BudgetId.php b/app/Helpers/Csv/Converter/BudgetId.php index 27e759ecbf..d246e7c060 100644 --- a/app/Helpers/Csv/Converter/BudgetId.php +++ b/app/Helpers/Csv/Converter/BudgetId.php @@ -19,7 +19,7 @@ class BudgetId extends BasicConverter implements ConverterInterface public function convert(): Budget { /** @var BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository = app(BudgetRepositoryInterface::class); $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $budget = $repository->find($value); diff --git a/app/Helpers/Csv/Converter/BudgetName.php b/app/Helpers/Csv/Converter/BudgetName.php index 1aa5e954eb..00c4e8d3ff 100644 --- a/app/Helpers/Csv/Converter/BudgetName.php +++ b/app/Helpers/Csv/Converter/BudgetName.php @@ -20,7 +20,7 @@ class BudgetName extends BasicConverter implements ConverterInterface public function convert(): Budget { /** @var BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository = app(BudgetRepositoryInterface::class); // is mapped? Then it's easy! if (isset($this->mapped[$this->index][$this->value])) { diff --git a/app/Helpers/Csv/Converter/CategoryId.php b/app/Helpers/Csv/Converter/CategoryId.php index 02ae657a91..b28b62a224 100644 --- a/app/Helpers/Csv/Converter/CategoryId.php +++ b/app/Helpers/Csv/Converter/CategoryId.php @@ -19,7 +19,7 @@ class CategoryId extends BasicConverter implements ConverterInterface public function convert(): Category { /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface'); + $repository = app(SingleCategoryRepositoryInterface::class); $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $category = $repository->find($value); diff --git a/app/Helpers/Csv/Converter/CategoryName.php b/app/Helpers/Csv/Converter/CategoryName.php index 26df88b8c5..c987bc3192 100644 --- a/app/Helpers/Csv/Converter/CategoryName.php +++ b/app/Helpers/Csv/Converter/CategoryName.php @@ -20,7 +20,7 @@ class CategoryName extends BasicConverter implements ConverterInterface public function convert(): Category { /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface'); + $repository = app(SingleCategoryRepositoryInterface::class); // is mapped? Then it's easy! if (isset($this->mapped[$this->index][$this->value])) { diff --git a/app/Helpers/Csv/Converter/CurrencyCode.php b/app/Helpers/Csv/Converter/CurrencyCode.php index 47a6fe5ee7..f77aadc8cb 100644 --- a/app/Helpers/Csv/Converter/CurrencyCode.php +++ b/app/Helpers/Csv/Converter/CurrencyCode.php @@ -19,7 +19,7 @@ class CurrencyCode extends BasicConverter implements ConverterInterface public function convert(): TransactionCurrency { /** @var CurrencyRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); + $repository = app(CurrencyRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { $currency = $repository->find($this->mapped[$this->index][$this->value]); diff --git a/app/Helpers/Csv/Converter/CurrencyId.php b/app/Helpers/Csv/Converter/CurrencyId.php index 3ab3f0fd3a..73ea0defba 100644 --- a/app/Helpers/Csv/Converter/CurrencyId.php +++ b/app/Helpers/Csv/Converter/CurrencyId.php @@ -19,7 +19,7 @@ class CurrencyId extends BasicConverter implements ConverterInterface public function convert(): TransactionCurrency { /** @var CurrencyRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); + $repository = app(CurrencyRepositoryInterface::class); $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $currency = $repository->find($value); diff --git a/app/Helpers/Csv/Converter/CurrencyName.php b/app/Helpers/Csv/Converter/CurrencyName.php index f4a020d430..e517df855b 100644 --- a/app/Helpers/Csv/Converter/CurrencyName.php +++ b/app/Helpers/Csv/Converter/CurrencyName.php @@ -19,7 +19,7 @@ class CurrencyName extends BasicConverter implements ConverterInterface public function convert(): TransactionCurrency { /** @var CurrencyRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); + $repository = app(CurrencyRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { $currency = $repository->find($this->mapped[$this->index][$this->value]); diff --git a/app/Helpers/Csv/Converter/CurrencySymbol.php b/app/Helpers/Csv/Converter/CurrencySymbol.php index e89a5fbe64..5235fbc889 100644 --- a/app/Helpers/Csv/Converter/CurrencySymbol.php +++ b/app/Helpers/Csv/Converter/CurrencySymbol.php @@ -19,7 +19,7 @@ class CurrencySymbol extends BasicConverter implements ConverterInterface public function convert(): TransactionCurrency { /** @var CurrencyRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); + $repository = app(CurrencyRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { $currency = $repository->find($this->mapped[$this->index][$this->value]); diff --git a/app/Helpers/Csv/Converter/OpposingAccountIban.php b/app/Helpers/Csv/Converter/OpposingAccountIban.php index e75c3b5abd..902e7df0aa 100644 --- a/app/Helpers/Csv/Converter/OpposingAccountIban.php +++ b/app/Helpers/Csv/Converter/OpposingAccountIban.php @@ -21,7 +21,7 @@ class OpposingAccountIban extends BasicConverter implements ConverterInterface public function convert() { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { $account = $repository->find($this->mapped[$this->index][$this->value]); diff --git a/app/Helpers/Csv/Converter/OpposingAccountId.php b/app/Helpers/Csv/Converter/OpposingAccountId.php index 742a4e3263..a28fbe0e2b 100644 --- a/app/Helpers/Csv/Converter/OpposingAccountId.php +++ b/app/Helpers/Csv/Converter/OpposingAccountId.php @@ -20,7 +20,7 @@ class OpposingAccountId extends BasicConverter implements ConverterInterface public function convert(): Account { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $account = $repository->find($value); diff --git a/app/Helpers/Csv/Converter/OpposingAccountName.php b/app/Helpers/Csv/Converter/OpposingAccountName.php index 302a224803..bda3aa1938 100644 --- a/app/Helpers/Csv/Converter/OpposingAccountName.php +++ b/app/Helpers/Csv/Converter/OpposingAccountName.php @@ -21,7 +21,7 @@ class OpposingAccountName extends BasicConverter implements ConverterInterface public function convert() { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); if (isset($this->mapped[$this->index][$this->value])) { $account = $repository->find($this->mapped[$this->index][$this->value]); diff --git a/app/Helpers/Csv/Converter/TagsComma.php b/app/Helpers/Csv/Converter/TagsComma.php index a38d64cce8..3bd448628e 100644 --- a/app/Helpers/Csv/Converter/TagsComma.php +++ b/app/Helpers/Csv/Converter/TagsComma.php @@ -19,7 +19,7 @@ class TagsComma extends BasicConverter implements ConverterInterface public function convert(): Collection { /** @var TagRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); + $repository = app(TagRepositoryInterface::class); $tags = new Collection; $strings = explode(',', $this->value); diff --git a/app/Helpers/Csv/Converter/TagsSpace.php b/app/Helpers/Csv/Converter/TagsSpace.php index 60eeba8b7d..5b1ee2cab3 100644 --- a/app/Helpers/Csv/Converter/TagsSpace.php +++ b/app/Helpers/Csv/Converter/TagsSpace.php @@ -19,7 +19,7 @@ class TagsSpace extends BasicConverter implements ConverterInterface public function convert(): Collection { /** @var TagRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); + $repository = app(TagRepositoryInterface::class); $tags = new Collection; diff --git a/app/Helpers/Csv/PostProcessing/AssetAccount.php b/app/Helpers/Csv/PostProcessing/AssetAccount.php index 542c72e0be..2d3195b7fd 100644 --- a/app/Helpers/Csv/PostProcessing/AssetAccount.php +++ b/app/Helpers/Csv/PostProcessing/AssetAccount.php @@ -6,6 +6,7 @@ use Auth; use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Log; use Validator; @@ -244,8 +245,8 @@ class AssetAccount implements PostProcessorInterface } } // create new if not exists and return that one: - /** @var \FireflyIII\Repositories\Account\AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); $accountData = [ 'name' => $accountNumber, 'accountType' => 'asset', diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index b82e96c7a5..a7a4d75d13 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -87,7 +87,7 @@ class ReportController extends Controller $budget = $budgetRepository->find(intval($attributes['budgetId'])); /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountRepository = app(AccountRepositoryInterface::class); $account = $accountRepository->find(intval($attributes['accountId'])); switch (true) { @@ -177,7 +177,7 @@ class ReportController extends Controller private function expenseEntry(array $attributes): string { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); $account = $repository->find(intval($attributes['accountId'])); $journals = $repository->getExpensesByDestination($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); $view = view('popup.report.expense-entry', compact('journals', 'account'))->render(); @@ -196,7 +196,7 @@ class ReportController extends Controller private function incomeEntry(array $attributes): string { /** @var AccountRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository = app(AccountRepositoryInterface::class); $account = $repository->find(intval($attributes['accountId'])); $journals = $repository->getIncomeByDestination($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); $view = view('popup.report.income-entry', compact('journals', 'account'))->render(); diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 4354a815af..2bd4837ff7 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -147,7 +147,7 @@ class ReportController extends Controller private function auditReport(Carbon $start, Carbon $end, Collection $accounts) { /** @var ARI $repos */ - $repos = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repos = app(ARI::class); $auditData = []; $dayBefore = clone $start; $dayBefore->subDay(); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 2883837f27..7e6040fc2b 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -289,7 +289,7 @@ class TransactionController extends Controller { $subTitle = trans('firefly.mass_edit_journals'); /** @var ARI $accountRepository */ - $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountRepository = app(ARI::class); $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); // put previous url in session diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index fbae113952..bf02a92306 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -263,7 +263,7 @@ class BillRepository implements BillRepositoryInterface { /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $accountRepository = app(AccountRepositoryInterface::class); $amount = '0'; $creditCards = $accountRepository->getCreditCards($end); // Find credit card accounts and possibly unpaid credit card bills. /** @var Account $creditCard */ From 3344bb7263a211b85784cf3267b6e7e3fd0534da Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 May 2016 20:49:19 +0200 Subject: [PATCH 073/206] Code cleanup. --- app/Helpers/Report/BudgetReportHelper.php | 9 ++--- app/Helpers/Report/ReportHelper.php | 14 ++++---- app/Http/Controllers/CategoryController.php | 2 +- .../Controllers/Chart/AccountController.php | 3 +- app/Http/Controllers/Chart/BillController.php | 3 +- .../Controllers/Chart/BudgetController.php | 5 +-- .../Controllers/Chart/CategoryController.php | 9 ++--- .../Controllers/Chart/PiggyBankController.php | 5 +-- .../Controllers/Chart/ReportController.php | 5 +-- app/Http/Controllers/CsvController.php | 6 ++-- .../Controllers/Popup/ReportController.php | 7 ++-- app/Http/Controllers/ReportController.php | 12 ++++--- app/Http/Middleware/Range.php | 5 +-- .../Attachment/AttachmentRepository.php | 5 +-- .../Category/SingleCategoryRepository.php | 33 +++++++---------- .../SingleCategoryRepositoryInterface.php | 16 +++------ .../ExportJob/ExportJobRepository.php | 35 +++++++++++++------ .../Journal/JournalRepository.php | 9 ++--- app/Rules/Actions/SetBudget.php | 2 +- app/Support/Events/BillScanner.php | 5 +-- app/Validation/FireflyValidator.php | 4 +-- 21 files changed, 105 insertions(+), 89 deletions(-) diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 75c50408d5..ccfc7099c1 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -16,6 +16,7 @@ use FireflyIII\Helpers\Collection\Budget as BudgetCollection; use FireflyIII\Helpers\Collection\BudgetLine; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Illuminate\Support\Collection; /** @@ -36,8 +37,8 @@ class BudgetReportHelper implements BudgetReportHelperInterface public function getBudgetReport(Carbon $start, Carbon $end, Collection $accounts): BudgetCollection { $object = new BudgetCollection; - /** @var \FireflyIII\Repositories\Budget\BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); $set = $repository->getBudgets(); $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end); $allTotalSpent = $repository->spentAllPerDayForAccounts($accounts, $start, $end); @@ -116,8 +117,8 @@ class BudgetReportHelper implements BudgetReportHelperInterface */ public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection { - /** @var \FireflyIII\Repositories\Budget\BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); $budgets = $repository->getActiveBudgets(); $set = new Collection; diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index 1e8156f82b..f3d942c580 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -14,7 +14,9 @@ use FireflyIII\Models\Bill; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; @@ -64,8 +66,8 @@ class ReportHelper implements ReportHelperInterface */ public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection { - /** @var \FireflyIII\Repositories\Bill\BillRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); $bills = $repository->getBillsForAccounts($accounts); $journals = $repository->getAllJournalsInRange($bills, $start, $end); $collection = new BillCollection; @@ -109,8 +111,8 @@ class ReportHelper implements ReportHelperInterface */ public function getCategoriesWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection { - /** @var \FireflyIII\Repositories\Category\CategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); $collection = $repository->earnedForAccountsPerMonth($accounts, $start, $end); $second = $repository->spentForAccountsPerMonth($accounts, $start, $end); $collection = $collection->merge($second); @@ -147,7 +149,7 @@ class ReportHelper implements ReportHelperInterface * GET CATEGORIES: */ /** @var \FireflyIII\Repositories\Category\CategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); + $repository = app(CategoryRepositoryInterface::class); $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); foreach ($set as $category) { @@ -209,7 +211,7 @@ class ReportHelper implements ReportHelperInterface public function listOfMonths(Carbon $date): array { /** @var FiscalHelperInterface $fiscalHelper */ - $fiscalHelper = app('FireflyIII\Helpers\FiscalHelperInterface'); + $fiscalHelper = app(FiscalHelperInterface::class); $start = clone $date; $start->startOfMonth(); $end = Carbon::now(); diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index e949b6d53c..896451a850 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -230,7 +230,7 @@ class CategoryController extends Controller $pageSize = Preferences::get('transactionPageSize', 50)->data; $set = $repository->getJournalsInRange($category, $start, $end, $page, $pageSize); - $count = $repository->countJournalsInRange($category, $start, $end); + $count = $repository->countJournals($category, $start, $end); $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); $journals->setPath('categories/show/' . $category->id . '/' . $date); diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 951e9bda4f..8a30017f99 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; @@ -30,7 +31,7 @@ class AccountController extends Controller { parent::__construct(); // create chart generator: - $this->generator = app('FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface'); + $this->generator = app(AccountChartGeneratorInterface::class); } diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index 4e92a28f24..00316ed43e 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionJournal; @@ -29,7 +30,7 @@ class BillController extends Controller { parent::__construct(); // create chart generator: - $this->generator = app('FireflyIII\Generator\Chart\Bill\BillChartGeneratorInterface'); + $this->generator = app(BillChartGeneratorInterface::class); } /** diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index ff32d20343..537b037a3c 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; @@ -34,7 +35,7 @@ class BudgetController extends Controller { parent::__construct(); // create chart generator: - $this->generator = app('FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface'); + $this->generator = app(BudgetChartGeneratorInterface::class); } /** @@ -331,7 +332,7 @@ class BudgetController extends Controller } /** @var BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository = app(BudgetRepositoryInterface::class); // loop over period, add by users range: $current = clone $start; $viewRange = Preferences::get('viewRange', '1M')->data; diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 1298c0ea0f..a84fccd578 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -5,6 +5,7 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; @@ -29,7 +30,7 @@ class CategoryController extends Controller const KEEP_POSITIVE = 1; - /** @var \FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface */ + /** @var CategoryChartGeneratorInterface */ protected $generator; /** @@ -39,7 +40,7 @@ class CategoryController extends Controller { parent::__construct(); // create chart generator: - $this->generator = app('FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface'); + $this->generator = app(CategoryChartGeneratorInterface::class); } @@ -198,7 +199,7 @@ class CategoryController extends Controller public function multiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) { /** @var CRI $repository */ - $repository = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); + $repository = app(CRI::class); // chart properties for cache: $cache = new CacheProperties(); @@ -295,7 +296,7 @@ class CategoryController extends Controller } /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface'); + $repository = app(SingleCategoryRepositoryInterface::class); // loop over period, add by users range: $current = clone $start; $viewRange = Preferences::get('viewRange', '1M')->data; diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php index ac3430d345..1acc1eaf87 100644 --- a/app/Http/Controllers/Chart/PiggyBankController.php +++ b/app/Http/Controllers/Chart/PiggyBankController.php @@ -3,6 +3,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Chart; +use FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; @@ -20,7 +21,7 @@ use Response; class PiggyBankController extends Controller { - /** @var \FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface */ + /** @var PiggyBankChartGeneratorInterface */ protected $generator; /** @@ -30,7 +31,7 @@ class PiggyBankController extends Controller { parent::__construct(); // create chart generator: - $this->generator = app('FireflyIII\Generator\Chart\PiggyBank\PiggyBankChartGeneratorInterface'); + $this->generator = app(PiggyBankChartGeneratorInterface::class); } /** diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index 1a3daad4de..d6eae001b8 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -5,6 +5,7 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; +use FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface; use FireflyIII\Helpers\Report\ReportQueryInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Support\CacheProperties; @@ -20,7 +21,7 @@ use Steam; class ReportController extends Controller { - /** @var \FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface */ + /** @var ReportChartGeneratorInterface */ protected $generator; /** @@ -30,7 +31,7 @@ class ReportController extends Controller { parent::__construct(); // create chart generator: - $this->generator = app('FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface'); + $this->generator = app(ReportChartGeneratorInterface::class); } /** diff --git a/app/Http/Controllers/CsvController.php b/app/Http/Controllers/CsvController.php index 39199726dd..feec892c4a 100644 --- a/app/Http/Controllers/CsvController.php +++ b/app/Http/Controllers/CsvController.php @@ -42,8 +42,8 @@ class CsvController extends Controller throw new FireflyException('CSV Import is not enabled.'); } - $this->wizard = app('FireflyIII\Helpers\Csv\WizardInterface'); - $this->data = app('FireflyIII\Helpers\Csv\Data'); + $this->wizard = app(WizardInterface::class); + $this->data = app(Data::class); } @@ -313,7 +313,7 @@ class CsvController extends Controller Log::debug('Created importer'); /** @var Importer $importer */ - $importer = app('FireflyIII\Helpers\Csv\Importer'); + $importer = app(Importer::class); $importer->setData($this->data); $importer->run(); Log::debug('Done importing!'); diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index a7a4d75d13..13edc40765 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -14,6 +14,7 @@ namespace FireflyIII\Http\Controllers\Popup; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collection\BalanceLine; +use FireflyIII\Helpers\Csv\Mapper\Budget; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -83,7 +84,7 @@ class ReportController extends Controller $role = intval($attributes['role']); /** @var BudgetRepositoryInterface $budgetRepository */ - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $budgetRepository = app(BudgetRepositoryInterface::class); $budget = $budgetRepository->find(intval($attributes['budgetId'])); /** @var AccountRepositoryInterface $accountRepository */ @@ -133,7 +134,7 @@ class ReportController extends Controller // then search for expenses in the given period // list them in some table format. /** @var BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository = app(BudgetRepositoryInterface::class); $budget = $repository->find(intval($attributes['budgetId'])); if (is_null($budget->id)) { $journals = $repository->getWithoutBudgetForAccounts($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); @@ -158,7 +159,7 @@ class ReportController extends Controller private function categoryEntry(array $attributes): string { /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface'); + $repository = app(SingleCategoryRepositoryInterface::class); $category = $repository->find(intval($attributes['categoryId'])); $journals = $repository->getJournalsForAccountsInRange($category, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); $view = view('popup.report.category-entry', compact('journals', 'category'))->render(); diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 2bd4837ff7..4759d19f54 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -9,6 +9,8 @@ use FireflyIII\Helpers\Report\ReportHelperInterface; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use Illuminate\Support\Collection; use Log; use Preferences; @@ -44,9 +46,9 @@ class ReportController extends Controller parent::__construct(); $this->helper = $helper; - $this->accountHelper = app('FireflyIII\Helpers\Report\AccountReportHelperInterface'); - $this->budgetHelper = app('FireflyIII\Helpers\Report\BudgetReportHelperInterface'); - $this->balanceHelper = app('FireflyIII\Helpers\Report\BalanceReportHelperInterface'); + $this->accountHelper = app(AccountReportHelperInterface::class); + $this->budgetHelper = app(BudgetReportHelperInterface::class); + $this->balanceHelper = app(BalanceReportHelperInterface::class); View::share('title', trans('firefly.reports')); View::share('mainTitleIcon', 'fa-line-chart'); @@ -269,8 +271,8 @@ class ReportController extends Controller $incomeTopLength = 8; $expenseTopLength = 8; // list of users stuff: - $budgets = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface')->getActiveBudgets(); - $categories = app('FireflyIII\Repositories\Category\CategoryRepositoryInterface')->getCategories(); + $budgets = app(BudgetRepositoryInterface::class)->getActiveBudgets(); + $categories = app(CategoryRepositoryInterface::class)->getCategories(); $accountReport = $this->accountHelper->getAccountReport($start, $end, $accounts); $incomes = $this->helper->getIncomeReport($start, $end, $accounts); $expenses = $this->helper->getExpenseReport($start, $end, $accounts); diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index 43b8d5420f..d63cbb74d7 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -6,6 +6,7 @@ namespace FireflyIII\Http\Middleware; use Carbon\Carbon; use Closure; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Contracts\Auth\Guard; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -66,8 +67,8 @@ class Range Session::put('end', $end); } if (!Session::has('first')) { - /** @var \FireflyIII\Repositories\Journal\JournalRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); $journal = $repository->first(); if (!is_null($journal->id)) { Session::put('first', $journal->date); diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 8ce7d08524..73dd96444d 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -3,6 +3,7 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Attachment; +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\User; use Illuminate\Support\Collection; @@ -34,8 +35,8 @@ class AttachmentRepository implements AttachmentRepositoryInterface */ public function destroy(Attachment $attachment): bool { - /** @var \FireflyIII\Helpers\Attachments\AttachmentHelperInterface $helper */ - $helper = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface'); + /** @var AttachmentHelperInterface $helper */ + $helper = app(AttachmentHelperInterface::class); $file = $helper->getAttachmentLocation($attachment); unlink($file); diff --git a/app/Repositories/Category/SingleCategoryRepository.php b/app/Repositories/Category/SingleCategoryRepository.php index 1c6e35f4b9..3c6ba443ff 100644 --- a/app/Repositories/Category/SingleCategoryRepository.php +++ b/app/Repositories/Category/SingleCategoryRepository.php @@ -33,27 +33,24 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate } /** - * @param Category $category + * @param Category $category + * @param Carbon|null $start + * @param Carbon|null $end * * @return int */ - public function countJournals(Category $category): int + public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int { - return $category->transactionjournals()->count(); + $query = $category->transactionjournals(); + if (!is_null($start)) { + $query->after($start); + } + if (!is_null($end)) { + $query->before($end); + } - } + return $query->count(); - /** - * @param Category $category - * - * @param Carbon $start - * @param Carbon $end - * - * @return int - */ - public function countJournalsInRange(Category $category, Carbon $start, Carbon $end): int - { - return $category->transactionjournals()->before($end)->after($start)->count(); } /** @@ -153,11 +150,7 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate { $offset = $page > 0 ? $page * $pageSize : 0; - return $category->transactionjournals()->expanded()->take($pageSize)->offset($offset) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->get(TransactionJournal::queryFields()); + return $category->transactionjournals()->expanded()->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); } diff --git a/app/Repositories/Category/SingleCategoryRepositoryInterface.php b/app/Repositories/Category/SingleCategoryRepositoryInterface.php index 7e0e5c9110..0aa59aa5e0 100644 --- a/app/Repositories/Category/SingleCategoryRepositoryInterface.php +++ b/app/Repositories/Category/SingleCategoryRepositoryInterface.php @@ -16,21 +16,13 @@ interface SingleCategoryRepositoryInterface { /** - * @param Category $category + * @param Category $category + * @param Carbon|null $start + * @param Carbon|null $end * * @return int */ - public function countJournals(Category $category): int; - - /** - * @param Category $category - * - * @param Carbon $start - * @param Carbon $end - * - * @return int - */ - public function countJournalsInRange(Category $category, Carbon $start, Carbon $end): int; + public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int; /** * @param Category $category diff --git a/app/Repositories/ExportJob/ExportJobRepository.php b/app/Repositories/ExportJob/ExportJobRepository.php index c84486bfdb..a5d53b630b 100644 --- a/app/Repositories/ExportJob/ExportJobRepository.php +++ b/app/Repositories/ExportJob/ExportJobRepository.php @@ -68,16 +68,26 @@ class ExportJobRepository implements ExportJobRepositoryInterface */ public function create(): ExportJob { - $exportJob = new ExportJob; - $exportJob->user()->associate($this->user); - /* - * In theory this random string could give db error. - */ - $exportJob->key = Str::random(12); - $exportJob->status = 'export_status_never_started'; - $exportJob->save(); + $count = 0; + while ($count < 30) { + $key = Str::random(12); + $existing = $this->findByKey($key); + if (is_null($existing->id)) { + $exportJob = new ExportJob; + $exportJob->user()->associate($this->user); + $exportJob->key = Str::random(12); + $exportJob->status = 'export_status_never_started'; + $exportJob->save(); + // breaks the loop: + + return $exportJob; + } + $count++; + + } + + return new ExportJob; - return $exportJob; } /** @@ -90,7 +100,12 @@ class ExportJobRepository implements ExportJobRepositoryInterface */ public function findByKey(string $key): ExportJob { - return $this->user->exportJobs()->where('key', $key)->first(); + $result = $this->user->exportJobs()->where('key', $key)->first(); + if (is_null($result)) { + return new ExportJob; + } + + return $result; } } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 09738bd6d3..b90617f5a5 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -14,6 +14,7 @@ use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator; @@ -309,8 +310,8 @@ class JournalRepository implements JournalRepositoryInterface */ private function saveTags(TransactionJournal $journal, array $array): bool { - /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */ - $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); + /** @var TagRepositoryInterface $tagRepository */ + $tagRepository = app(TagRepositoryInterface::class); foreach ($array as $name) { if (strlen(trim($name)) > 0) { @@ -434,8 +435,8 @@ class JournalRepository implements JournalRepositoryInterface private function updateTags(TransactionJournal $journal, array $array): bool { // create tag repository - /** @var \FireflyIII\Repositories\Tag\TagRepositoryInterface $tagRepository */ - $tagRepository = app('FireflyIII\Repositories\Tag\TagRepositoryInterface'); + /** @var TagRepositoryInterface $tagRepository */ + $tagRepository = app(TagRepositoryInterface::class); // find or create all tags: diff --git a/app/Rules/Actions/SetBudget.php b/app/Rules/Actions/SetBudget.php index 9ea73e2a39..0cf9cc9584 100644 --- a/app/Rules/Actions/SetBudget.php +++ b/app/Rules/Actions/SetBudget.php @@ -46,7 +46,7 @@ class SetBudget implements ActionInterface public function act(TransactionJournal $journal): bool { /** @var BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository = app(BudgetRepositoryInterface::class); $search = $this->action->action_value; $budgets = $repository->getActiveBudgets(); $budget = $budgets->filter( diff --git a/app/Support/Events/BillScanner.php b/app/Support/Events/BillScanner.php index a4d858dfeb..53b7f5790d 100644 --- a/app/Support/Events/BillScanner.php +++ b/app/Support/Events/BillScanner.php @@ -12,6 +12,7 @@ namespace FireflyIII\Support\Events; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; /** * Class BillScanner @@ -25,8 +26,8 @@ class BillScanner */ public static function scan(TransactionJournal $journal) { - /** @var \FireflyIII\Repositories\Bill\BillRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); $list = $journal->user->bills()->where('active', 1)->where('automatch', 1)->get(); /** @var \FireflyIII\Models\Bill $bill */ diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 337d005e3f..ba95ca1c4a 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -60,7 +60,7 @@ class FireflyValidator extends Validator $secret = Session::get('two-factor-secret'); /** @var Google2FA $google2fa */ - $google2fa = app('PragmaRX\Google2FA\Google2FA'); + $google2fa = app(Google2FA::class); return $google2fa->verifyKey($secret, $value); } @@ -138,7 +138,7 @@ class FireflyValidator extends Validator return true; case 'set_budget': /** @var BudgetRepositoryInterface $repository */ - $repository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository = app(BudgetRepositoryInterface::class); $budgets = $repository->getBudgets(); // count budgets, should have at least one $count = $budgets->filter( From cb1cb9f328c138275242f2a70e94fcb7ddfeef32 Mon Sep 17 00:00:00 2001 From: zjean Date: Wed, 4 May 2016 22:09:42 +0200 Subject: [PATCH 074/206] Force https schema if APP_FORCE_SSL=true in .env --- .env.example | 1 + .env.testing | 1 + app/Providers/AppServiceProvider.php | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index dcca3ab865..59778fd8b8 100755 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ APP_ENV=production APP_DEBUG=false +APP_FORCE_SSL=true APP_KEY=SomeRandomStringOf32CharsExactly LOG_LEVEL=warning diff --git a/.env.testing b/.env.testing index 852745ab42..8aeacddc74 100755 --- a/.env.testing +++ b/.env.testing @@ -1,5 +1,6 @@ APP_ENV=testing APP_DEBUG=true +APP_FORCE_SSL=false APP_KEY=SomeRandomStringOf32CharsExactly LOG_LEVEL=debug diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 544d2465a9..27b2a18de0 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -20,7 +20,10 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { - // + // force https urls + if (env('APP_FORCE_SSL', false)) { + \URL::forceSchema('https'); + } } /** From 36f3eb8b2f6a1e4f8c4fcc82ebebb555cc9a6795 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 07:09:12 +0200 Subject: [PATCH 075/206] Small fix in journal handler. --- app/Http/Controllers/TransactionController.php | 2 +- app/Http/Requests/JournalFormRequest.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 7e6040fc2b..9aefd01b86 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -464,7 +464,7 @@ class TransactionController extends Controller Session::flash('info', $att->getMessages()->get('attachments')); } - event(new TransactionJournalStored($journal, intval($request->get('piggy_bank_id')))); + event(new TransactionJournalStored($journal, intval($journalData['piggy_bank_id']))); Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); Preferences::mark(); diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 8b8886b76b..db26820994 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -51,6 +51,7 @@ class JournalFormRequest extends Request 'budget_id' => intval($this->get('budget_id')), 'category' => $this->get('category') ?? '', 'tags' => explode(',', $tags), + 'piggy_bank_id' => $this->get('piggy_bank_id') ? intval($this->get('piggy_bank_id')) : 0, ]; } @@ -71,7 +72,7 @@ class JournalFormRequest extends Request 'interest_date' => 'date', 'category' => 'between:1,255', 'amount_currency_id_amount' => 'required|exists:transaction_currencies,id', - + 'piggy_bank_id' => 'numeric', ]; switch ($what) { From b211d72c8b51cb22358ac219e8341f6d163c64d0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 07:45:29 +0200 Subject: [PATCH 076/206] Static data must remain static data. --- app/Support/ExpandedForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index e56e13b466..8f503e7117 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -343,7 +343,7 @@ class ExpandedForm $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); $classes = $this->getHolderClasses($name); - $value = $this->fillFieldValue($name, $value); + //$value = $this->fillFieldValue($name, $value); $html = view('form.static', compact('classes', 'name', 'label', 'value', 'options'))->render(); return $html; From 5a8abe004e417d5649dc57d342e1e188295bcc00 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 07:45:40 +0200 Subject: [PATCH 077/206] Field rename --- public/js/ff/split/journal/from-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js index 18aea1a0b9..4cbb57d3d7 100644 --- a/public/js/ff/split/journal/from-store.js +++ b/public/js/ff/split/journal/from-store.js @@ -42,7 +42,7 @@ function calculateSum() { $('.amount-warning').remove(); if (sum != originalSum) { console.log(sum + ' does not match ' + originalSum); - var holder = $('#amount_holder'); + var holder = $('#journal_amount_holder'); var par = holder.find('p.form-control-static'); var amount = $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par); } From 16dc8b7d686d1dd9b345662f2978075e4712cd8e Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 07:45:52 +0200 Subject: [PATCH 078/206] Translation for error. --- resources/lang/en_US/validation.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 2638f9e75f..fa086c9fbb 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -17,6 +17,7 @@ return [ 'file_attached' => 'Succesfully uploaded file ":name".', 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', 'file_too_large' => 'File ":name" is too large.', + 'belongs_to_user' => 'The value of :attribute is unknown', 'accepted' => 'The :attribute must be accepted.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', From 0b747076388ac531aa21253b48b8b4b2574f81b5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 07:46:11 +0200 Subject: [PATCH 079/206] Display and handle errors. --- .../views/split/journals/from-store.twig | 129 ++++++++++-------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig index 8de8d22426..6f41cde1be 100644 --- a/resources/views/split/journals/from-store.twig +++ b/resources/views/split/journals/from-store.twig @@ -5,7 +5,7 @@ {% endblock %} {% block content %} {{ Form.open({'class' : 'form-horizontal','id' : 'store','url' : route('split.journal.from-store.post')}) }} - +
@@ -30,25 +30,32 @@ {{ trans(('firefly.split_intro_three_'~data.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} {% endif %}

- +

This feature is experimental.

+ {% if errors.all()|length > 0 %} +
+
+
+
+

{{ 'errors'|_ }}

+
+ +
+
+
+
    + {% for key, err in errors.all() %} +
  • {{ err }}
  • + {% endfor %} +
+
+
+
+
+ {% endif %}
@@ -60,23 +67,24 @@
- {{ ExpandedForm.text('journal_description', data.description) }} - {{ ExpandedForm.select('currency', currencies, data.amount_currency_id_amount) }} - {{ ExpandedForm.staticText('amount', data.amount|formatAmount) }} + {{ ExpandedForm.text('journal_description') }} + {{ ExpandedForm.select('journal_currency_id', currencies, data.journal_currency_id) }} + {{ ExpandedForm.staticText('journal_amount', data.journal_amount|formatAmount) }} + {% if data.what == 'withdrawal' or data.what == 'transfer' %} - {{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.source_account_id]) }} - + {{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.journal_source_account_id]) }} + {% endif %} {% if data.what == 'deposit' %} - {{ ExpandedForm.staticText('revenue_account', data.source_account_name) }} - + {{ ExpandedForm.staticText('revenue_account', data.journal_source_account_name) }} + {% endif %} {% if data.what == 'transfer' %} - {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }} - + {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.journal_destination_account_id]) }} + {% endif %}
@@ -143,43 +151,48 @@ - - #1 - {{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }} - - - {% if data.what == 'withdrawal' %} + {% for index, descr in data.description %} + + #{{ loop.index }} - {{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }} + - {% endif %} - - {% if data.what == 'deposit' %} + + {% if data.what == 'withdrawal' %} + + + + {% endif %} + + + {% if data.what == 'deposit' %} + + {{ Form.select('destination_account_id[]', assetAccounts, data.destination_account_id[index], {class: 'form-control'}) }} + + {% endif %} + + + + + + {% if data.what == 'withdrawal' %} + + {{ Form.select('budget_id[]', budgets, data.budget_id[index], {class: 'form-control'}) }} + + {% endif %} - {{ Form.select('destination_account_id[]', assetAccounts, data.destination_account_id, {class: 'form-control'}) }} + - {% endif %} - - - - - {{ Form.input('number', 'amount[]', data.amount, {class: 'form-control', autocomplete: 'off', step: 'any', min:'0.01'}) }} - - {% if data.what == 'withdrawal' %} - - {{ Form.select('budget[]', budgets, data.budget_id, {class: 'form-control'}) }} - - {% endif %} - - {{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }} - - {% if data.what == 'transfer' %} - - {{ Form.select('piggy_bank_id[]', piggyBanks, data.piggy_bank_id, {class: 'form-control'}) }} - - {% endif %} - + {% if data.what == 'transfer' %} + + {{ Form.select('piggy_bank_id[]', piggyBanks, data.piggy_bank_id[index], {class: 'form-control'}) }} + + {% endif %} + + {% endfor %}

@@ -200,7 +213,7 @@ {% endblock %} {% block scripts %} From 21a197ba4695228c526ab2edde02b306ab9d2084 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 07:46:19 +0200 Subject: [PATCH 080/206] More translations. --- resources/lang/en_US/firefly.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index ddf18123a8..843459a908 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -826,7 +826,9 @@ return [ 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', 'store_splitted_transfer' => 'Store splitted transfer', + 'add_another_split' => 'Add another split', + 'split-transactions' => 'Split transactions', + 'split-new-transaction' => 'Split a new transaction', - 'add_another_split' => 'Add another split', ]; From eb3d2b1749ac9909cc1f55d72ad349013d442987 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 18:59:46 +0200 Subject: [PATCH 081/206] Building split transactions and fixing tests. --- app/Crud/Split/Journal.php | 4 +- .../Transaction/SplitController.php | 110 ++++++++++++++++-- .../Controllers/TransactionController.php | 5 + app/Http/Requests/SplitJournalFormRequest.php | 51 ++++---- app/Models/TransactionJournal.php | 35 +----- .../Account/AccountRepository.php | 42 +++---- app/Repositories/Budget/BudgetRepository.php | 31 +---- .../Category/SingleCategoryRepository.php | 18 ++- .../Journal/JournalRepository.php | 16 ++- app/Support/Migration/TestData.php | 2 +- .../Models/TransactionJournalSupport.php | 32 ++--- app/Validation/FireflyValidator.php | 4 +- config/app.php | 1 + database/seeds/SplitDataSeeder.php | 68 +++++++++-- phpunit.xml | 31 +++++ .../views/split/journals/from-store.twig | 8 +- .../Controllers/JsonControllerTest.php | 6 +- .../Controllers/TransactionControllerTest.php | 12 +- 18 files changed, 317 insertions(+), 159 deletions(-) create mode 100644 phpunit.xml diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php index 9315ba9193..bd0bfeb380 100644 --- a/app/Crud/Split/Journal.php +++ b/app/Crud/Split/Journal.php @@ -53,8 +53,8 @@ class Journal implements JournalInterface [ 'user_id' => $this->user->id, 'transaction_type_id' => $transactionType->id, - 'transaction_currency_id' => $data['currency_id'], - 'description' => $data['description'], + 'transaction_currency_id' => $data['journal_currency_id'], + 'description' => $data['journal_description'], 'completed' => 0, 'date' => $data['date'], 'interest_date' => $data['interest_date'], diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 913cd6cf97..62870495d3 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -19,8 +19,10 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Illuminate\Http\Request; use Log; use Session; +use View; /** * Class SplitController @@ -32,8 +34,31 @@ class SplitController extends Controller /** * */ - public function journalFromStore() + public function __construct() { + parent::__construct(); + View::share('mainTitleIcon', 'fa-share-alt'); + View::share('title', trans('firefly.split-transactions')); + } + + + /** + * @param Request $request + * + * @return mixed + * @throws FireflyException + */ + public function journalFromStore(Request $request) + { + if ($request->old('journal_currency_id')) { + $preFilled = $this->arrayFromOldData($request->old()); + } else { + $preFilled = $this->arrayFromSession(); + } + + Session::flash('preFilled', $preFilled); + View::share('subTitle', trans('firefly.split-new-transaction')); + /** @var CurrencyRepositoryInterface $currencyRepository */ $currencyRepository = app(CurrencyRepositoryInterface::class); /** @var AccountRepositoryInterface $accountRepository */ @@ -45,20 +70,16 @@ class SplitController extends Controller /** @var PiggyBankRepositoryInterface $piggyBankRepository */ $piggyBankRepository = app(PiggyBankRepositoryInterface::class); - // expect data to be in session or in post? - $journalData = session('temporary_split_data'); + $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyBankRepository->getPiggyBanks()); - if (!is_array($journalData)) { - throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. - } - Log::debug('Journal data', $journalData); + //Session::flash('warning', 'This feature is very experimental. Beware.'); - return view('split.journals.from-store', compact('currencies', 'piggyBanks', 'assetAccounts', 'budgets'))->with('data', $journalData); + return view('split.journals.from-store', compact('currencies', 'piggyBanks', 'assetAccounts', 'budgets'))->with('data', $preFilled); } @@ -77,6 +98,9 @@ class SplitController extends Controller $journal = $repository->storeJournal($data); // Then, store each transaction individually. + if (is_null($journal->id)) { + throw new FireflyException('Could not store transaction.'); + } foreach ($data['transactions'] as $transaction) { $transactions = $repository->storeTransaction($journal, $transaction); } @@ -92,4 +116,74 @@ class SplitController extends Controller return redirect(session('transactions.create.url')); } + /** + * @param array $old + * + * @return array + */ + private function arrayFromOldData(array $old): array + { + // this array is pretty much equal to what we expect it to be. + Log::debug('Prefilled', $old); + + return $old; + } + + /** + * @return array + * @throws FireflyException + */ + private function arrayFromSession(): array + { + // expect data to be in session or in post? + $data = session('temporary_split_data'); + + if (!is_array($data)) { + Log::error('Could not find transaction data in your session. Please go back and try again.', ['data' => $data]); // translate me. + throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. + } + + Log::debug('Journal data', $data); + + $preFilled = [ + 'what' => $data['what'], + 'journal_description' => $data['description'], + 'journal_source_account_id' => $data['source_account_id'], + 'journal_source_account_name' => $data['source_account_name'], + 'journal_destination_account_id' => $data['destination_account_id'], + 'journal_destination_account_name' => $data['destination_account_name'], + 'journal_amount' => $data['amount'], + 'journal_currency_id' => $data['amount_currency_id_amount'], + 'date' => $data['date'], + 'interest_date' => $data['interest_date'], + 'book_date' => $data['book_date'], + 'process_date' => $data['process_date'], + + 'description' => [], + 'destination_account_id' => [], + 'destination_account_name' => [], + 'amount' => [], + 'budget_id' => [], + 'category' => [], + 'piggy_bank_id' => [], + ]; + + // create the first transaction: + $preFilled['description'][] = $data['description']; + $preFilled['destination_account_id'][] = $data['destination_account_id']; + $preFilled['destination_account_name'][] = $data['destination_account_name']; + $preFilled['amount'][] = $data['amount']; + $preFilled['budget_id'][] = $data['budget_id']; + $preFilled['category'][] = $data['category']; + $preFilled['piggy_bank_id'][] = $data['piggy_bank_id']; + + // echo '

';
+        //        var_dump($data);
+        //        var_dump($preFilled);
+        //        exit;
+        Log::debug('Prefilled', $preFilled);
+
+        return $preFilled;
+    }
+
 }
\ No newline at end of file
diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php
index 9aefd01b86..71871aabaa 100644
--- a/app/Http/Controllers/TransactionController.php
+++ b/app/Http/Controllers/TransactionController.php
@@ -30,6 +30,7 @@ use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
 use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
 use Illuminate\Support\Collection;
 use Input;
+use Log;
 use Preferences;
 use Response;
 use Session;
@@ -437,6 +438,7 @@ class TransactionController extends Controller
      */
     public function store(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att)
     {
+        Log::debug('Start of store.');
         $doSplit     = intval($request->get('split_journal')) === 1;
         $journalData = $request->getJournalData();
         if ($doSplit) {
@@ -445,6 +447,7 @@ class TransactionController extends Controller
 
             return redirect(route('split.journal.from-store'));
         }
+        Log::debug('Not in split.');
 
         // if not withdrawal, unset budgetid.
         if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
@@ -493,6 +496,8 @@ class TransactionController extends Controller
     public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal)
     {
         $journalData = $request->getJournalData();
+        Log::debug('Will update journal ', $journal->toArray());
+        Log::debug('Update related data ', $journalData);
         $repository->update($journal, $journalData);
 
         // save attachments:
diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php
index aad2dd05ba..526fd30c45 100644
--- a/app/Http/Requests/SplitJournalFormRequest.php
+++ b/app/Http/Requests/SplitJournalFormRequest.php
@@ -35,26 +35,29 @@ class SplitJournalFormRequest extends Request
     public function getSplitData(): array
     {
         $data = [
-            'description'         => $this->get('journal_description'),
-            'currency_id'         => intval($this->get('currency')),
-            'source_account_id'   => intval($this->get('source_account_id')),
-            'source_account_name' => $this->get('source_account_name'),
-            'date'                => new Carbon($this->get('date')),
-            'what'                => $this->get('what'),
-            'interest_date'       => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null,
-            'book_date'           => $this->get('book_date') ? new Carbon($this->get('book_date')) : null,
-            'process_date'        => $this->get('process_date') ? new Carbon($this->get('process_date')) : null,
-            'transactions'        => [],
+            'journal_description'              => $this->get('journal_description'),
+            'journal_currency_id'              => intval($this->get('journal_currency_id')),
+            'journal_source_account_id'        => intval($this->get('journal_source_account_id')),
+            'journal_source_account_name'      => $this->get('journal_source_account_name'),
+            'journal_destination_account_id'   => intval($this->get('journal_source_destination_id')),
+            'journal_destination_account_name' => $this->get('journal_source_destination_name'),
+            'date'                             => new Carbon($this->get('date')),
+            'what'                             => $this->get('what'),
+            'interest_date'                    => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null,
+            'book_date'                        => $this->get('book_date') ? new Carbon($this->get('book_date')) : null,
+            'process_date'                     => $this->get('process_date') ? new Carbon($this->get('process_date')) : null,
+            'transactions'                     => [],
         ];
+
         // description is leading because it is one of the mandatory fields.
         foreach ($this->get('description') as $index => $description) {
             $transaction            = [
                 'description'              => $description,
                 'amount'                   => round($this->get('amount')[$index], 2),
-                'budget_id'                => $this->get('budget')[$index] ? intval($this->get('budget')[$index]) : 0,
+                'budget_id'                => $this->get('budget_id')[$index] ? intval($this->get('budget_id')[$index]) : 0,
                 'category'                 => $this->get('category')[$index] ?? '',
-                'source_account_id'        => intval($this->get('source_account_id')),
-                'source_account_name'      => $this->get('source_account_name'),
+                'source_account_id'        => intval($this->get('journal_source_account_id')),
+                'source_account_name'      => $this->get('journal_source_account_name'),
                 'destination_account_id'   => isset($this->get('destination_account_id')[$index])
                     ? intval($this->get('destination_account_id')[$index])
                     : intval($this->get('destination_account_id')),
@@ -72,21 +75,23 @@ class SplitJournalFormRequest extends Request
     public function rules(): array
     {
         return [
-            'journal_description'        => 'required|between:1,255',
-            'currency'                   => 'required|exists:transaction_currencies,id',
-            'source_account_id'          => 'numeric|belongsToUser:accounts,id',
-            'source_account_name.*'      => 'between:1,255',
-            'what'                       => 'required|in:withdrawal,deposit,transfer',
-            'date'                       => 'required|date',
-            'interest_date'              => 'date',
-            'book_date'                  => 'date',
-            'process_date'               => 'date',
+            'what'                          => 'required|in:withdrawal,deposit,transfer',
+            'journal_description'           => 'required|between:1,255',
+            'journal_source_account_id'     => 'numeric|belongsToUser:accounts,id',
+            'journal_source_account_name.*' => 'between:1,255',
+            'journal_currency_id'           => 'required|exists:transaction_currencies,id',
+            'date'                          => 'required|date',
+            'interest_date'                 => 'date',
+            'book_date'                     => 'date',
+            'process_date'                  => 'date',
+
             'description.*'              => 'required|between:1,255',
             'destination_account_id.*'   => 'numeric|belongsToUser:accounts,id',
             'destination_account_name.*' => 'between:1,255',
             'amount.*'                   => 'required|numeric',
-            'budget.*'                   => 'belongsToUser:budgets,id',
+            'budget_id.*'                => 'belongsToUser:budgets,id',
             'category.*'                 => 'between:1,255',
+            'piggy_bank_id.*'            => 'between:1,255',
         ];
     }
 }
\ No newline at end of file
diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php
index b6ed728650..6a16338f95 100644
--- a/app/Models/TransactionJournal.php
+++ b/app/Models/TransactionJournal.php
@@ -3,7 +3,6 @@
 use Auth;
 use Carbon\Carbon;
 use Crypt;
-use DB;
 use FireflyIII\Support\Models\TransactionJournalSupport;
 use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
 use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -325,7 +324,6 @@ class TransactionJournal extends TransactionJournalSupport
      */
     public function scopeExpanded(EloquentBuilder $query)
     {
-        $query->distinct();
         // left join transaction type:
         if (!self::isJoined($query, 'transaction_types')) {
             $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
@@ -336,39 +334,12 @@ class TransactionJournal extends TransactionJournalSupport
 
         // left join destination (for amount and account info).
         $query->leftJoin(
-            'transactions as destination', function (JoinClause $join) {
-            $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
-                 ->where('destination.amount', '>', 0);
+            'transactions', function (JoinClause $join) {
+            $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0);
         }
         );
-        // join destination account
-        $query->leftJoin('accounts as destination_account', 'destination_account.id', '=', 'destination.account_id');
-        // join destination account type
-        $query->leftJoin('account_types as destination_acct_type', 'destination_account.account_type_id', '=', 'destination_acct_type.id');
-
-        // left join source (for amount and account info).
-        $query->leftJoin(
-            'transactions as source', function (JoinClause $join) {
-            $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
-                 ->where('source.amount', '<', 0);
-        }
-        );
-        // join destination account
-        $query->leftJoin('accounts as source_account', 'source_account.id', '=', 'source.account_id');
-        // join destination account type
-        $query->leftJoin('account_types as source_acct_type', 'source_account.account_type_id', '=', 'source_acct_type.id')
-              ->orderBy('transaction_journals.date', 'DESC')->orderBy('transaction_journals.order', 'ASC')->orderBy('transaction_journals.id', 'DESC');
-
-        // something else:
-        $query->where(DB::raw('`destination`.`amount` * -1'),'=',DB::raw('`source`.`amount`'));
-
-        // group:
         $query->groupBy('transaction_journals.id');
-        $query->groupBy('source.id');
-
-        $query->with(['categories', 'budgets', 'attachments', 'bill']);
-
-
+        $query->with(['categories', 'budgets', 'attachments', 'bill','transactions']);
     }
 
     /**
diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php
index 44d9bee43f..198c9bd9b6 100644
--- a/app/Repositories/Account/AccountRepository.php
+++ b/app/Repositories/Account/AccountRepository.php
@@ -15,6 +15,7 @@ use FireflyIII\Models\TransactionType;
 use FireflyIII\User;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Query\JoinClause;
 use Illuminate\Pagination\LengthAwarePaginator;
 use Illuminate\Support\Collection;
 use Log;
@@ -232,23 +233,20 @@ class AccountRepository implements AccountRepositoryInterface
      */
     public function getFrontpageTransactions(Account $account, Carbon $start, Carbon $end): Collection
     {
-        $set = $this->user
+        $query = $this->user
             ->transactionjournals()
             ->expanded()
-            ->where('source_account.id', $account->id)
-            //            ->with(['transactions'])
-            //            ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
-            //            ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')->where('accounts.id', $account->id)
-            //            ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id')
-            //            ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
             ->before($end)
-            ->after($start)
-            //            ->orderBy('transaction_journals.date', 'DESC')
-            //            ->orderBy('transaction_journals.order', 'ASC')
-            //            ->orderBy('transaction_journals.id', 'DESC')
-            ->take(10)
-            //            ->get(['transaction_journals.*', 'transaction_currencies.symbol', 'transaction_types.type']);
-            ->get(TransactionJournal::queryFields());
+            ->after($start);
+
+        // expand query:
+        $query->leftJoin(
+            'transactions as source', function (JoinClause $join) {
+            $join->on('source.transaction_journal_id', '=', 'transaction_journals.id');
+        }
+        )->where('source.account_id', $account->id);
+
+        $set = $query->get(TransactionJournal::queryFields());
 
         return $set;
     }
@@ -290,13 +288,15 @@ class AccountRepository implements AccountRepositoryInterface
         $offset = ($page - 1) * $pageSize;
         $query  = $this->user
             ->transactionJournals()
-            ->expanded()
-            ->where(
-                function (Builder $q) use ($account) {
-                    $q->where('source_account.id', $account->id);
-                    $q->orWhere('destination_account.id', $account->id);
-                }
-            );
+            ->expanded();
+
+        // expand query:
+        $query->leftJoin(
+            'transactions as source', function (JoinClause $join) {
+            $join->on('source.transaction_journal_id', '=', 'transaction_journals.id');
+        }
+        )->where('source.account_id', $account->id);
+
 
         $count     = $query->count();
         $set       = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php
index 1e90caf829..c897a171f2 100644
--- a/app/Repositories/Budget/BudgetRepository.php
+++ b/app/Repositories/Budget/BudgetRepository.php
@@ -568,32 +568,9 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
     public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection
     {
         $query = $budget->limitrepetitions()
-                        ->where( // valid when either of these are true:
-                            function ($q) use ($start, $end) {
-                                $q->where(
-                                    function ($query) use ($start, $end) {
-                                        // starts before start time, and the end also after start time.
-                                        $query->where('limit_repetitions.startdate', '<=', $start->format('Y-m-d 00:00:00'));
-                                        $query->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00'));
-                                    }
-                                );
-                                $q->orWhere(
-                                    function ($query) use ($start, $end) {
-                                        // end after end time, and start is before end time
-                                        $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'));
-                                        $query->where('limit_repetitions.enddate', '>=', $end->format('Y-m-d 00:00:00'));
-                                    }
-                                );
-                                // start is after start and end is before end
-                                $q->orWhere(
-                                    function ($query) use ($start, $end) {
-                                        // end after end time, and start is before end time
-                                        $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'));
-                                        $query->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d 00:00:00'));
-                                    }
-                                );
-                            }
-                        );
+            // starts before start time, and the end also after start time.
+                        ->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00'))
+                        ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'));
         if (!is_null($ignore->id)) {
             $query->where('limit_repetitions.id', '!=', $ignore->id);
         }
@@ -683,7 +660,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
             }
             )
             ->whereIn('transactions.account_id', $ids)
-            ->having('transaction_count', '=', 1)
+            //->having('transaction_count', '=', 1) TODO check if this still works
             ->transactionTypes([TransactionType::WITHDRAWAL])
             ->first(
                 [
diff --git a/app/Repositories/Category/SingleCategoryRepository.php b/app/Repositories/Category/SingleCategoryRepository.php
index 3c6ba443ff..7918ceac72 100644
--- a/app/Repositories/Category/SingleCategoryRepository.php
+++ b/app/Repositories/Category/SingleCategoryRepository.php
@@ -10,6 +10,7 @@ use FireflyIII\Models\TransactionJournal;
 use FireflyIII\Models\TransactionType;
 use FireflyIII\Repositories\Shared\ComponentRepository;
 use FireflyIII\User;
+use Illuminate\Database\Query\JoinClause;
 use Illuminate\Support\Collection;
 
 /**
@@ -90,9 +91,16 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate
                           ->after($start)
                           ->groupBy('transaction_journals.date');
 
+        $query->leftJoin(
+            'transactions as destination', function (JoinClause $join) {
+            $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
+        }
+        );
+
+
         if ($accounts->count() > 0) {
             $ids = $accounts->pluck('id')->toArray();
-            $query->whereIn('destination_account.id', $ids);
+            $query->whereIn('destination.account.id', $ids);
         }
 
         $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`destination`.`amount`) AS `sum`')]);
@@ -258,13 +266,17 @@ class SingleCategoryRepository extends ComponentRepository implements SingleCate
                           ->before($end)
                           ->after($start)
                           ->groupBy('transaction_journals.date');
+        $query->leftJoin(
+            'transactions as source', function (JoinClause $join) {
+            $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
+        }
+        );
 
         if ($accounts->count() > 0) {
             $ids = $accounts->pluck('id')->toArray();
-            $query->whereIn('source_account.id', $ids);
+            $query->whereIn('source.account_id', $ids);
         }
 
-
         $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`source`.`amount`) AS `sum`')]);
 
         $return = [];
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index b90617f5a5..617c87e9f8 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -336,31 +336,36 @@ class JournalRepository implements JournalRepositoryInterface
     {
         $sourceAccount      = null;
         $destinationAccount = null;
+        Log::debug('Now in storeAccounts()');
         switch ($type->type) {
             case TransactionType::WITHDRAWAL:
+                Log::debug('Now in storeAccounts()::withdrawal');
                 list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data);
                 break;
 
             case TransactionType::DEPOSIT:
+                Log::debug('Now in storeAccounts()::deposit');
                 list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data);
 
                 break;
             case TransactionType::TRANSFER:
+                Log::debug('Now in storeAccounts()::transfer');
                 $sourceAccount      = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
                 $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
                 break;
             default:
                 throw new FireflyException('Did not recognise transaction type.');
         }
+        Log::debug('Now in storeAccounts(), continued.');
 
         if (is_null($destinationAccount)) {
-            Log::error('"to"-account is null, so we cannot continue!', ['data' => $data]);
-            throw new FireflyException('"to"-account is null, so we cannot continue!');
+            Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
+            throw new FireflyException('"destination"-account is null, so we cannot continue!');
         }
 
         if (is_null($sourceAccount)) {
-            Log::error('"from"-account is null, so we cannot continue!', ['data' => $data]);
-            throw new FireflyException('"from"-account is null, so we cannot continue!');
+            Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]);
+            throw new FireflyException('"source"-account is null, so we cannot continue!');
 
         }
 
@@ -402,8 +407,10 @@ class JournalRepository implements JournalRepositoryInterface
     private function storeWithdrawalAccounts(array $data): array
     {
         $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
+        Log::debug('Now in storeWithdrawalAccounts() with ', ['name' => $data['destination_account_name'], 'len' => strlen($data['destination_account_name'])]);
 
         if (strlen($data['destination_account_name']) > 0) {
+            Log::debug('Now in storeWithdrawalAccounts()');
             $destinationType    = AccountType::where('type', 'Expense account')->first();
             $destinationAccount = Account::firstOrCreateEncrypted(
                 [
@@ -413,6 +420,7 @@ class JournalRepository implements JournalRepositoryInterface
                     'active'          => 1,
                 ]
             );
+            Log::debug('Errors: ', ['err' => $destinationAccount->getErrors()->toArray(), 'id' => $destinationAccount->id]);
 
             return [$sourceAccount, $destinationAccount];
         }
diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php
index c383da8e09..9a921e704d 100644
--- a/app/Support/Migration/TestData.php
+++ b/app/Support/Migration/TestData.php
@@ -367,7 +367,7 @@ class TestData
     public static function createExpenseAccounts(User $user): bool
     {
         $expenses = ['Adobe', 'Google', 'Vitens', 'Albert Heijn', 'PLUS', 'Apple', 'Bakker', 'Belastingdienst', 'bol.com', 'Cafe Central', 'conrad.nl',
-                     'Coolblue', 'Shell',
+                     'Coolblue', 'Shell', 'SixtyFive', 'EightyFour', 'Fiftyone',
                      'DUO', 'Etos', 'FEBO', 'Greenchoice', 'Halfords', 'XS4All', 'iCentre', 'Jumper', 'Land lord'];
         foreach ($expenses as $name) {
             // create account:
diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php
index 71cea6ea8d..37e9a69603 100644
--- a/app/Support/Models/TransactionJournalSupport.php
+++ b/app/Support/Models/TransactionJournalSupport.php
@@ -56,8 +56,21 @@ class TransactionJournalSupport extends Model
             return $journal->destination_amount;
         }
 
-        $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount');
-        if ($journal->isDeposit()) {
+        // saves on queries:
+        $amount = '0';
+        if (count($journal->transactions) === 0) {
+            $amount = '0';
+            foreach ($journal->transactions as $t) {
+                if ($t->amount > 0) {
+                    $amount = bcadd($amount, $t->amount);
+                }
+            }
+        }
+        if ($amount === '0') {
+            $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount');
+        }
+
+        if ($journal->isWithdrawal()) {
             $amount = $amount * -1;
         }
         $amount = strval($amount);
@@ -234,19 +247,8 @@ class TransactionJournalSupport extends Model
             'transaction_journals.*',
             'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent.
             'transaction_currencies.code AS transaction_currency_code',
-            // all for destination:
-            //'destination.amount AS destination_amount', // is always positive
-            DB::raw('SUM(`destination`.`amount`) as `destination_amount`'),
-            'destination_account.id AS destination_account_id',
-            'destination_account.name AS destination_account_name',
-            'destination_acct_type.type AS destination_account_type',
-            // all for source:
-            //'source.amount AS source_amount', // is always negative
-            DB::raw('SUM(`source`.`amount`) as `source_amount`'),
-            'source_account.id AS source_account_id',
-            'source_account.name AS source_account_name',
-            'source_acct_type.type AS source_account_type',
-            DB::raw('COUNT(`destination`.`id`) + COUNT(`source`.`id`) as `count_transactions`'),
+            DB::raw('SUM(`transactions`.`amount`) as `amount`'),
+            DB::raw('(COUNT(`transactions`.`id`) * 2) as `count_transactions`'),
         ];
     }
 
diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php
index ba95ca1c4a..b7bb92b1bd 100644
--- a/app/Validation/FireflyValidator.php
+++ b/app/Validation/FireflyValidator.php
@@ -76,7 +76,9 @@ class FireflyValidator extends Validator
     {
         $field = $parameters[1] ?? 'id';
 
-
+        if (intval($value) === 0) {
+            return true;
+        }
         $count = DB::table($parameters[0])->where('user_id', Auth::user()->id)->where($field, $value)->count();
         if ($count === 1) {
             return true;
diff --git a/config/app.php b/config/app.php
index 8ba2732c0d..0f9602b381 100644
--- a/config/app.php
+++ b/config/app.php
@@ -154,6 +154,7 @@ return [
         /*
          * More service providers.
          */
+        FireflyIII\Providers\CrudServiceProvider::class,
         FireflyIII\Providers\AccountServiceProvider::class,
         FireflyIII\Providers\AttachmentServiceProvider::class,
         FireflyIII\Providers\BillServiceProvider::class,
diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php
index 96c350e993..1e4c864b8e 100644
--- a/database/seeds/SplitDataSeeder.php
+++ b/database/seeds/SplitDataSeeder.php
@@ -90,7 +90,7 @@ class SplitDataSeeder extends Seeder
          * Create splitted expense of 66,-
          */
         $today = new Carbon;
-        $today->subDays(6);
+        $today->subDays(2);
 
         if (!$skipWithdrawal) {
             $journal = TransactionJournal::create(
@@ -98,7 +98,7 @@ class SplitDataSeeder extends Seeder
                     'user_id'                 => $user->id,
                     'transaction_type_id'     => 1, // withdrawal
                     'transaction_currency_id' => 1,
-                    'description'             => 'Split Expense (journal)',
+                    'description'             => 'Split Even Expense (journal (50/50))',
                     'completed'               => 1,
                     'date'                    => $today->format('Y-m-d'),
                 ]
@@ -107,10 +107,11 @@ class SplitDataSeeder extends Seeder
             // split in 6 transactions (multiple destinations). 22,- each
             // source is TestData Checking Account.
             // also attach some budgets and stuff.
-            $destinations = ['Albert Heijn', 'PLUS', 'Apple'];
-            $budgets      = ['Groceries', 'Groceries', 'Car'];
-            $categories   = ['Bills', 'Bills', 'Car'];
-            $source       = TestData::findAccount($user, 'Checking Account');
+            $destinations = ['SixtyFive', 'EightyFour'];
+            $budgets      = ['Groceries', 'Car'];
+            $categories   = ['Bills', 'Bills'];
+            $amounts      = [50, 50];
+            $source       = TestData::findAccount($user, 'Alternate Checking Account');
             foreach ($destinations as $index => $dest) {
                 $bud         = $budgets[$index];
                 $cat         = $categories[$index];
@@ -120,7 +121,7 @@ class SplitDataSeeder extends Seeder
                     [
                         'account_id'             => $source->id,
                         'transaction_journal_id' => $journal->id,
-                        'amount'                 => '-22',
+                        'amount'                 => $amounts[$index] * -1,
 
                     ]
                 );
@@ -129,7 +130,7 @@ class SplitDataSeeder extends Seeder
                     [
                         'account_id'             => $destination->id,
                         'transaction_journal_id' => $journal->id,
-                        'amount'                 => '22',
+                        'amount'                 => $amounts[$index],
 
                     ]
                 );
@@ -141,6 +142,57 @@ class SplitDataSeeder extends Seeder
                 $two->categories()->save(TestData::findCategory($user, $cat));
             }
         }
+        // AND ANOTHER ONE
+        $today->addDay();
+        $journal = TransactionJournal::create(
+            [
+                'user_id'                 => $user->id,
+                'transaction_type_id'     => 1, // withdrawal
+                'transaction_currency_id' => 1,
+                'description'             => 'Split Uneven Expense (journal (15/34/51=100))',
+                'completed'               => 1,
+                'date'                    => $today->format('Y-m-d'),
+            ]
+        );
+
+        // split in 6 transactions (multiple destinations). 22,- each
+        // source is TestData Checking Account.
+        // also attach some budgets and stuff.
+        $destinations = ['SixtyFive', 'EightyFour', 'Fiftyone'];
+        $budgets      = ['Groceries', 'Groceries', 'Car'];
+        $categories   = ['Bills', 'Bills', 'Car'];
+        $amounts      = [15, 34, 51];
+        $source       = TestData::findAccount($user, 'Checking Account');
+        foreach ($destinations as $index => $dest) {
+            $bud         = $budgets[$index];
+            $cat         = $categories[$index];
+            $destination = TestData::findAccount($user, $dest);
+
+            $one = Transaction::create(
+                [
+                    'account_id'             => $source->id,
+                    'transaction_journal_id' => $journal->id,
+                    'amount'                 => $amounts[$index] * -1,
+
+                ]
+            );
+
+            $two = Transaction::create(
+                [
+                    'account_id'             => $destination->id,
+                    'transaction_journal_id' => $journal->id,
+                    'amount'                 => $amounts[$index],
+
+                ]
+            );
+
+            $one->budgets()->save(TestData::findBudget($user, $bud));
+            $two->budgets()->save(TestData::findBudget($user, $bud));
+
+            $one->categories()->save(TestData::findCategory($user, $cat));
+            $two->categories()->save(TestData::findCategory($user, $cat));
+        }
+
         // create splitted income of 99,-
         $today->addDay();
 
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000000..31ac89bcfe
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,31 @@
+
+
+    
+        
+            ./tests/
+        
+    
+    
+        
+            app/
+        
+    
+    
+        
+        
+        
+        
+    
+
+    
+        
+    
+
diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig
index 6f41cde1be..10e1f88d5c 100644
--- a/resources/views/split/journals/from-store.twig
+++ b/resources/views/split/journals/from-store.twig
@@ -73,18 +73,18 @@
                     
                     
                     {% if data.what == 'withdrawal' or data.what == 'transfer' %}
-                        {{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.journal_source_account_id]) }}
-                        
+                        {{ ExpandedForm.staticText('journal_asset_source_account', assetAccounts[data.journal_source_account_id]) }}
+                        
                     {% endif %}
                     
                     {% if data.what == 'deposit' %}
                         {{ ExpandedForm.staticText('revenue_account', data.journal_source_account_name) }}
-                        
+                        
                     {% endif %}
                     
                     {% if data.what == 'transfer' %}
                         {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.journal_destination_account_id]) }}
-                        
+                        
                     {% endif %}
                 

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≠d760&MNG`WhF&srnL^3Rq#qD@@r6O1`wUg0^ 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 L#xVarO$G)4LY46e diff --git a/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 b/public/lib/font-awesome/fonts/fontawesome-webfont.woff2 index 500e5172534171f678e01f7569d66f9257036a09..cab8571d588e7c3abc482f2c24fe407dad189ad3 100644 GIT binary patch literal 70728 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

diff --git a/tests/acceptance/Controllers/JsonControllerTest.php b/tests/acceptance/Controllers/JsonControllerTest.php index 140660f5f3..27e86491b0 100644 --- a/tests/acceptance/Controllers/JsonControllerTest.php +++ b/tests/acceptance/Controllers/JsonControllerTest.php @@ -155,11 +155,9 @@ class JsonControllerTest extends TestCase */ public function testTransactionJournals($range) { - $type = factory(FireflyIII\Models\TransactionType::class)->make(); $repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); - - $repository->shouldReceive('getTransactionType')->with('deposit')->once()->andReturn($type); - $repository->shouldReceive('getJournalsOfType')->with($type)->once()->andReturn(new Collection); + $paginator = new \Illuminate\Pagination\LengthAwarePaginator(new Collection, 0, 40); + $repository->shouldReceive('getJournals')->withAnyArgs()->once()->andReturn($paginator); $this->be($this->user()); $this->changeDateRange($this->user(), $range); diff --git a/tests/acceptance/Controllers/TransactionControllerTest.php b/tests/acceptance/Controllers/TransactionControllerTest.php index 76223b68a5..a5ce3b5364 100644 --- a/tests/acceptance/Controllers/TransactionControllerTest.php +++ b/tests/acceptance/Controllers/TransactionControllerTest.php @@ -68,7 +68,7 @@ class TransactionControllerTest extends TestCase public function testIndex($range) { $journals = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); - $journals->shouldReceive('getJournalsOfTypes')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); + $journals->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); $this->be($this->user()); $this->changeDateRange($this->user(), $range); @@ -117,8 +117,8 @@ class TransactionControllerTest extends TestCase $args = [ 'what' => 'withdrawal', 'description' => 'Something', - 'account_id' => '1', - 'expense_account' => 'Some expense', + 'source_account_id' => '1', + 'destination_account_name' => 'Some expense', 'amount' => 100, 'amount_currency_id_amount' => 1, 'date' => '2015-01-01', @@ -141,10 +141,10 @@ class TransactionControllerTest extends TestCase $args = [ 'what' => 'withdrawal', - 'id' => 2, + 'id' => 4, 'description' => 'Something new', - 'account_id' => '1', - 'expense_account' => 'Some expense', + 'source_account_id' => '1', + 'destination_account_name' => 'Some expense account', 'amount' => 100, 'amount_currency_id_amount' => 1, 'date' => '2015-01-01', From 5cc22f49cf25f40ff442108b3616a8ab8d4be2a1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 19:04:21 +0200 Subject: [PATCH 082/206] Fix tests. --- tests/acceptance/Controllers/TransactionControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/Controllers/TransactionControllerTest.php b/tests/acceptance/Controllers/TransactionControllerTest.php index a5ce3b5364..613104d265 100644 --- a/tests/acceptance/Controllers/TransactionControllerTest.php +++ b/tests/acceptance/Controllers/TransactionControllerTest.php @@ -150,7 +150,7 @@ class TransactionControllerTest extends TestCase 'date' => '2015-01-01', ]; $this->be($this->user()); - $this->call('POST', '/transaction/update/1', $args); + $this->call('POST', '/transaction/update/4', $args); $this->assertResponseStatus(302); $this->assertSessionHas('success'); } From e73d590ead7cb245df4dac169bcd8400dfc39369 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 19:05:29 +0200 Subject: [PATCH 083/206] Update composer.lock --- composer.lock | 1630 ++----------------------------------------------- 1 file changed, 59 insertions(+), 1571 deletions(-) diff --git a/composer.lock b/composer.lock index c0ae510416..0bd1d02478 100644 --- a/composer.lock +++ b/composer.lock @@ -51,16 +51,16 @@ }, { "name": "christian-riesen/base32", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/ChristianRiesen/base32.git", - "reference": "fde061a370b0a97fdcd33d9d5f7b1b70ce1f79d4" + "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/fde061a370b0a97fdcd33d9d5f7b1b70ce1f79d4", - "reference": "fde061a370b0a97fdcd33d9d5f7b1b70ce1f79d4", + "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", + "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", "shasum": "" }, "require": { @@ -101,7 +101,7 @@ "encode", "rfc4648" ], - "time": "2016-04-07 07:45:31" + "time": "2016-05-05 11:49:03" }, { "name": "classpreloader/classpreloader", @@ -855,16 +855,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.30", + "version": "v5.2.31", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ecfbbfbf96105439cc9a40bc78277bdb0268e34d" + "reference": "2fa2797604bf54b06faf7bb139a9fc0d66826fea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ecfbbfbf96105439cc9a40bc78277bdb0268e34d", - "reference": "ecfbbfbf96105439cc9a40bc78277bdb0268e34d", + "url": "https://api.github.com/repos/laravel/framework/zipball/2fa2797604bf54b06faf7bb139a9fc0d66826fea", + "reference": "2fa2797604bf54b06faf7bb139a9fc0d66826fea", "shasum": "" }, "require": { @@ -980,7 +980,7 @@ "framework", "laravel" ], - "time": "2016-04-19 19:12:18" + "time": "2016-04-27 13:02:09" }, { "name": "laravelcollective/html", @@ -1163,16 +1163,16 @@ }, { "name": "league/flysystem", - "version": "1.0.21", + "version": "1.0.22", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "35a83cf67d80d7040f306c77b0a84b9fbcc4fbfb" + "reference": "bd73a91703969a2d20ab4bfbf971d6c2cbe36612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/35a83cf67d80d7040f306c77b0a84b9fbcc4fbfb", - "reference": "35a83cf67d80d7040f306c77b0a84b9fbcc4fbfb", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd73a91703969a2d20ab4bfbf971d6c2cbe36612", + "reference": "bd73a91703969a2d20ab4bfbf971d6c2cbe36612", "shasum": "" }, "require": { @@ -1242,7 +1242,7 @@ "sftp", "storage" ], - "time": "2016-04-22 10:56:25" + "time": "2016-04-28 06:53:12" }, { "name": "monolog/monolog", @@ -1842,16 +1842,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.1", + "version": "v5.4.2", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421" + "reference": "d8db871a54619458a805229a057ea2af33c753e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/0697e6aa65c83edf97bb0f23d8763f94e3f11421", - "reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d8db871a54619458a805229a057ea2af33c753e8", + "reference": "d8db871a54619458a805229a057ea2af33c753e8", "shasum": "" }, "require": { @@ -1891,20 +1891,20 @@ "mail", "mailer" ], - "time": "2015-06-06 14:19:39" + "time": "2016-05-01 08:45:47" }, { "name": "symfony/console", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd" + "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6b1175135bc2a74c08a28d89761272de8beed8cd", - "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd", + "url": "https://api.github.com/repos/symfony/console/zipball/34a214710e0714b6efcf40ba3cd1e31373a97820", + "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820", "shasum": "" }, "require": { @@ -1951,11 +1951,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-03-16 17:00:50" + "time": "2016-04-28 09:48:42" }, { "name": "symfony/debug", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -2012,16 +2012,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39" + "reference": "17b04e6b1ede45b57d3ad5146abe50df6c3968b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9002dcf018d884d294b1ef20a6f968efc1128f39", - "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/17b04e6b1ede45b57d3ad5146abe50df6c3968b4", + "reference": "17b04e6b1ede45b57d3ad5146abe50df6c3968b4", "shasum": "" }, "require": { @@ -2068,11 +2068,11 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-03-10 10:34:12" + "time": "2016-04-12 18:09:53" }, { "name": "symfony/finder", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -2121,16 +2121,16 @@ }, { "name": "symfony/http-foundation", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f" + "reference": "18b24bc32d2495ae79d76e777368786a6536fe31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/99f38445a874e7becb8afc4b4a79ee181cf6ec3f", - "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/18b24bc32d2495ae79d76e777368786a6536fe31", + "reference": "18b24bc32d2495ae79d76e777368786a6536fe31", "shasum": "" }, "require": { @@ -2170,20 +2170,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-03-27 14:50:32" + "time": "2016-04-12 18:09:53" }, { "name": "symfony/http-kernel", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "579f828489659d7b3430f4bd9b67b4618b387dea" + "reference": "1aa25588241f915cf176b7c371e5d629dfff8b43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/579f828489659d7b3430f4bd9b67b4618b387dea", - "reference": "579f828489659d7b3430f4bd9b67b4618b387dea", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1aa25588241f915cf176b7c371e5d629dfff8b43", + "reference": "1aa25588241f915cf176b7c371e5d629dfff8b43", "shasum": "" }, "require": { @@ -2252,7 +2252,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2016-03-25 01:41:20" + "time": "2016-05-03 05:58:27" }, { "name": "symfony/polyfill-mbstring", @@ -2423,16 +2423,16 @@ }, { "name": "symfony/process", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776" + "reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e6f1f98bbd355d209a992bfff45e7edfbd4a0776", - "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776", + "url": "https://api.github.com/repos/symfony/process/zipball/53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb", + "reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb", "shasum": "" }, "require": { @@ -2468,20 +2468,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-03-30 10:41:14" + "time": "2016-04-14 15:30:28" }, { "name": "symfony/routing", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa" + "reference": "6ab6fd5ee754fb53a303a5621ae35f3afd5970ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/d061b609f2d0769494c381ec92f5c5cc5e4a20aa", - "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa", + "url": "https://api.github.com/repos/symfony/routing/zipball/6ab6fd5ee754fb53a303a5621ae35f3afd5970ac", + "reference": "6ab6fd5ee754fb53a303a5621ae35f3afd5970ac", "shasum": "" }, "require": { @@ -2543,11 +2543,11 @@ "uri", "url" ], - "time": "2016-03-23 13:23:25" + "time": "2016-04-28 09:48:42" }, { "name": "symfony/translation", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", @@ -2611,16 +2611,16 @@ }, { "name": "symfony/var-dumper", - "version": "v3.0.4", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79" + "reference": "0e918c269093ba4c77fca14e9424fa74ed16f1a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3841ed86527d18ee2c35fe4afb1b2fc60f8fae79", - "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e918c269093ba4c77fca14e9424fa74ed16f1a6", + "reference": "0e918c269093ba4c77fca14e9424fa74ed16f1a6", "shasum": "" }, "require": { @@ -2670,7 +2670,7 @@ "debug", "dump" ], - "time": "2016-03-10 10:34:12" + "time": "2016-04-25 11:17:47" }, { "name": "twig/twig", @@ -2839,1519 +2839,7 @@ "time": "2016-04-07 14:59:06" } ], - "packages-dev": [ - { - "name": "barryvdh/laravel-debugbar", - "version": "v2.2.0", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/13b7058d2120c8d5af7f1ada21b7c44dd87b666a", - "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a", - "shasum": "" - }, - "require": { - "illuminate/support": "5.1.*|5.2.*", - "maximebf/debugbar": "~1.11.0", - "php": ">=5.5.9", - "symfony/finder": "~2.7|~3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2-dev" - } - }, - "autoload": { - "psr-4": { - "Barryvdh\\Debugbar\\": "src/" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "PHP Debugbar integration for Laravel", - "keywords": [ - "debug", - "debugbar", - "laravel", - "profiler", - "webprofiler" - ], - "time": "2016-02-17 08:32:21" - }, - { - "name": "barryvdh/laravel-ide-helper", - "version": "v2.1.4", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/f1ebd847aac9a4545325d35108cafc285fe1605f", - "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f", - "shasum": "" - }, - "require": { - "illuminate/console": "5.0.x|5.1.x|5.2.x", - "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", - "illuminate/support": "5.0.x|5.1.x|5.2.x", - "php": ">=5.4.0", - "phpdocumentor/reflection-docblock": "^2.0.4", - "symfony/class-loader": "~2.3|~3.0" - }, - "require-dev": { - "doctrine/dbal": "~2.3" - }, - "suggest": { - "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "psr-4": { - "Barryvdh\\LaravelIdeHelper\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", - "keywords": [ - "autocomplete", - "codeintel", - "helper", - "ide", - "laravel", - "netbeans", - "phpdoc", - "phpstorm", - "sublime" - ], - "time": "2016-03-03 08:45:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14 21:17:01" - }, - { - "name": "fzaninotto/faker", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "d0190b156bcca848d401fb80f31f504f37141c8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d0190b156bcca848d401fb80f31f504f37141c8d", - "reference": "d0190b156bcca848d401fb80f31f504f37141c8d", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "suggest": { - "ext-intl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2015-05-29 06:29:14" - }, - { - "name": "hamcrest/hamcrest-php", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8bfb4013724c1f62dc267af0e998207ac3fdc226" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8bfb4013724c1f62dc267af0e998207ac3fdc226", - "reference": "8bfb4013724c1f62dc267af0e998207ac3fdc226", - "shasum": "" - }, - "require": { - "php": "^5.3|^7.0" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" - }, - "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "hamcrest" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "This is the PHP port of Hamcrest Matchers", - "keywords": [ - "test" - ], - "time": "2016-04-21 19:47:43" - }, - { - "name": "johnkary/phpunit-speedtrap", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/johnkary/phpunit-speedtrap.git", - "reference": "76a26f8a903a9434608cdad2b41c40cd134ea326" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/johnkary/phpunit-speedtrap/zipball/76a26f8a903a9434608cdad2b41c40cd134ea326", - "reference": "76a26f8a903a9434608cdad2b41c40cd134ea326", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*|~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "JohnKary": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Kary", - "email": "john@johnkary.net" - } - ], - "description": "Find slow tests in your PHPUnit test suite", - "homepage": "https://github.com/johnkary/phpunit-speedtrap", - "keywords": [ - "phpunit", - "profile", - "slow" - ], - "time": "2015-09-13 19:01:00" - }, - { - "name": "maximebf/debugbar", - "version": "v1.11.1", - "source": { - "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f", - "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.11-dev" - } - }, - "autoload": { - "psr-4": { - "DebugBar\\": "src/DebugBar/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": [ - "debug", - "debugbar" - ], - "time": "2016-01-22 12:22:23" - }, - { - "name": "mockery/mockery", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/padraic/mockery.git", - "reference": "4957f4086614e4fb4e710680f2c000dda0db1008" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/padraic/mockery/zipball/4957f4086614e4fb4e710680f2c000dda0db1008", - "reference": "4957f4086614e4fb4e710680f2c000dda0db1008", - "shasum": "" - }, - "require": { - "hamcrest/hamcrest-php": "^2.0@dev", - "lib-pcre": ">=7.0", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Mockery": "library/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" - } - ], - "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/padraic/mockery", - "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" - ], - "time": "2016-04-20 18:36:44" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2015-02-03 12:10:50" - }, - { - "name": "phpspec/prophecy", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpspec/phpspec": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2016-02-15 07:46:21" - }, - { - "name": "phpunit/php-code-coverage", - "version": "2.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2015-10-06 15:47:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2015-06-21 13:08:43" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21 13:50:34" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2015-06-21 08:01:12" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2015-09-15 10:49:45" - }, - { - "name": "phpunit/phpunit", - "version": "4.8.24", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1066c562c52900a142a0e2bbf0582994671385e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e", - "reference": "a1066c562c52900a142a0e2bbf0582994671385e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "~2.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": ">=1.0.6", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.8.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2016-03-14 06:16:08" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2015-10-02 06:51:40" - }, - { - "name": "sebastian/comparator", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2015-07-26 15:48:44" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08 07:14:41" - }, - { - "name": "sebastian/environment", - "version": "1.3.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-02-26 18:40:46" - }, - { - "name": "sebastian/exporter", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2015-06-21 07:55:53" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12 03:26:01" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" - }, - { - "name": "sebastian/version", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" - }, - { - "name": "symfony/class-loader", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/class-loader.git", - "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877", - "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "require-dev": { - "symfony/finder": "~2.8|~3.0", - "symfony/polyfill-apcu": "~1.1" - }, - "suggest": { - "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\ClassLoader\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony ClassLoader Component", - "homepage": "https://symfony.com", - "time": "2016-03-30 10:41:14" - }, - { - "name": "symfony/css-selector", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0", - "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2016-03-04 07:55:57" - }, - { - "name": "symfony/dom-crawler", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/18a06d7a9af41718c20764a674a0ebba3bc40d1f", - "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "~2.8|~3.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2016-03-23 13:23:25" - }, - { - "name": "symfony/yaml", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "0047c8366744a16de7516622c5b7355336afae96" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", - "reference": "0047c8366744a16de7516622c5b7355336afae96", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-03-04 07:55:57" - } - ], + "packages-dev": null, "aliases": [], "minimum-stability": "stable", "stability-flags": { From 4e1ff8c4a3af7801b5ce5719407828027fc4b91d Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 19:07:46 +0200 Subject: [PATCH 084/206] Removed phpunit.xml --- phpunit.xml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 phpunit.xml diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 31ac89bcfe..0000000000 --- a/phpunit.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - ./tests/ - - - - - app/ - - - - - - - - - - - - - From dd8b500efd1d2c953cef97614db228939684c020 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 21:25:20 +0200 Subject: [PATCH 085/206] These budget charts are the worst, I'm telling you. --- app/Helpers/Report/BudgetReportHelper.php | 4 +- app/Http/Controllers/BudgetController.php | 4 +- .../Controllers/Chart/AccountController.php | 2 +- app/Http/Controllers/Chart/BillController.php | 2 +- .../Controllers/Chart/BudgetController.php | 17 +- app/Http/Controllers/ReportController.php | 2 +- app/Models/Transaction.php | 169 +++++--- app/Repositories/Budget/BudgetRepository.php | 108 ++++-- .../Budget/BudgetRepositoryInterface.php | 18 +- .../Journal/JournalRepository.php | 4 + database/seeds/SplitDataSeeder.php | 361 +++++++++--------- 11 files changed, 379 insertions(+), 312 deletions(-) diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index ccfc7099c1..8a9de7eaec 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -124,8 +124,8 @@ class BudgetReportHelper implements BudgetReportHelperInterface $set = new Collection; /** @var Budget $budget */ foreach ($budgets as $budget) { - $expenses = $repository->getExpensesPerDay($budget, $start, $end, $accounts); - $total = strval($expenses->sum('dailyAmount')); + $expenses = $repository->spentPerDay($budget, $start, $end, $accounts); + $total = strval(array_sum($expenses)); if (bccomp($total, '0') === -1) { $set->push($budget); } diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 7b8164c693..0fb3acae7c 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -264,7 +264,7 @@ class BudgetController extends Controller $set = $budget->limitrepetitions()->orderBy('startdate', 'DESC')->get(); $subTitle = e($budget->name); $journals->setPath('/budgets/show/' . $budget->id); - $spentArray = $repository->spentPerDay($budget, $start, $end); + $spentArray = $repository->spentPerDay($budget, $start, $end, new Collection); $limits = new Collection(); /** @var LimitRepetition $entry */ @@ -298,7 +298,7 @@ class BudgetController extends Controller $set = new Collection([$repetition]); $subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]); $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id); - $spentArray = $repository->spentPerDay($budget, $start, $end); + $spentArray = $repository->spentPerDay($budget, $start, $end, new Collection); $limits = new Collection(); /** @var LimitRepetition $entry */ diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 8a30017f99..27e78f2142 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -13,7 +13,7 @@ use Illuminate\Support\Collection; use Preferences; use Response; -/** +/** checked * Class AccountController * * @package FireflyIII\Http\Controllers\Chart diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index 00316ed43e..f0b654f7f4 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -24,7 +24,7 @@ class BillController extends Controller protected $generator; /** - * + * checked */ public function __construct() { diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 537b037a3c..66814507ed 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -39,6 +39,8 @@ class BudgetController extends Controller } /** + * checked + * * @param BudgetRepositoryInterface $repository * @param Budget $budget * @@ -59,7 +61,7 @@ class BudgetController extends Controller $cache->addProperty('budget'); if ($cache->has()) { - return Response::json($cache->get()); + //return Response::json($cache->get()); } $final = clone $last; @@ -67,7 +69,7 @@ class BudgetController extends Controller $last = Navigation::endOfX($last, $range, $final); $entries = new Collection; // get all expenses: - $spentArray = $repository->spentPerDay($budget, $first, $last); + $spentArray = $repository->spentPerDay($budget, $first, $last, new Collection); while ($first < $last) { @@ -113,19 +115,14 @@ class BudgetController extends Controller return Response::json($cache->get()); } - $set = $repository->getExpensesPerDay($budget, $start, $end); + $set = $repository->spentPerDay($budget, $start, $end, new Collection); $entries = new Collection; $amount = $repetition->amount; // get sum (har har)! while ($start <= $end) { $formatted = $start->format('Y-m-d'); - $filtered = $set->filter( - function (Budget $obj) use ($formatted) { - return $obj->date == $formatted; - } - ); - $sum = is_null($filtered->first()) ? '0' : $filtered->first()->dailyAmount; + $sum = $set[$formatted] ?? '0'; /* * Sum of expenses on this day: @@ -135,7 +132,7 @@ class BudgetController extends Controller $start->addDay(); } - $data = $this->generator->budgetLimit($entries); + $data = $this->generator->budgetLimit($entries, 'monthAndDay'); $cache->store($data); return Response::json($data); diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 4759d19f54..ce34559df8 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -100,7 +100,7 @@ class ReportController extends Controller // lower threshold if ($start < session('first')) { - Log::debug('Start is ' . $start . ' but sessionfirst is ' . session('first')); + Log::debug('Start is ' . $start . ' but session first is ' . session('first')); $start = session('first'); } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 8417d5850a..a118f4ed15 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -1,7 +1,7 @@ 'between:1,255', 'amount' => 'required|numeric', ]; - protected $dates = ['created_at', 'updated_at', 'deleted_at']; use SoftDeletes, ValidatingTrait; + /** + * @param Builder $query + * @param string $table + * + * @return bool + */ + public static function isJoined(Builder $query, string $table):bool + { + $joins = $query->getQuery()->joins; + if (is_null($joins)) { + return false; + } + foreach ($joins as $join) { + if ($join->table === $table) { + return true; + } + } + + return false; + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ @@ -59,54 +80,6 @@ class Transaction extends Model return $this->belongsTo('FireflyIII\Models\Account'); } - /** - * @param $value - * - * @return float|int - */ - public function getAmountAttribute($value) - { - return $value; - } - - /** - * @param EloquentBuilder $query - * @param Carbon $date - * - * @return mixed - */ - public function scopeAfter(EloquentBuilder $query, Carbon $date) - { - return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00')); - } - - /** - * @param EloquentBuilder $query - * @param Carbon $date - * - * @return mixed - */ - public function scopeBefore(EloquentBuilder $query, Carbon $date) - { - return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); - } - - /** - * @param $value - */ - public function setAmountAttribute($value) - { - $this->attributes['amount'] = strval(round($value, 2)); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function transactionJournal() - { - return $this->belongsTo('FireflyIII\Models\TransactionJournal'); - } - /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ @@ -122,4 +95,74 @@ class Transaction extends Model { return $this->belongsToMany('FireflyIII\Models\Category'); } + + /** + * @param $value + * + * @return float|int + */ + public function getAmountAttribute($value) + { + return $value; + } + + /** + * + * @param Builder $query + * @param Carbon $date + */ + public function scopeAfter(Builder $query, Carbon $date) + { + if (!self::isJoined($query, 'transaction_journals')) { + $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'); + } + $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00')); + } + + /** + * + * @param Builder $query + * @param Carbon $date + * + */ + public function scopeBefore(Builder $query, Carbon $date) + { + if (!self::isJoined($query, 'transaction_journals')) { + $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'); + } + $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); + } + + /** + * + * @param Builder $query + * @param array $types + */ + public function scopeTransactionTypes(Builder $query, array $types) + { + if (!self::isJoined($query, 'transaction_journals')) { + $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'); + } + + if (!self::isJoined($query, 'transaction_types')) { + $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); + } + $query->whereIn('transaction_types.type', $types); + } + + /** + * @param $value + */ + public function setAmountAttribute($value) + { + $this->attributes['amount'] = strval(round($value, 2)); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function transactionJournal() + { + return $this->belongsTo('FireflyIII\Models\TransactionJournal'); + } } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index c897a171f2..c097f4b230 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -21,6 +21,7 @@ use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Input; +use Log; /** * Class BudgetRepository @@ -310,16 +311,19 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn * @param Carbon $start * @param Carbon $end * + * @deprecated + * * @return array */ public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array { + // get budgets, $ids = $accounts->pluck('id')->toArray(); $budgetIds = $budgets->pluck('id')->toArray(); /** @var Collection $set */ $set = $this->user->budgets() - ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->join('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') ->leftJoin( 'transactions', function (JoinClause $join) { @@ -340,6 +344,27 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ] ); + // run it again, for transactions this time. + /** @var Collection $secondSet */ + $secondSet = $this->user->budgets() + ->join('budget_transaction', 'budgets.id', '=', 'budget_transaction.budget_id') + ->leftJoin('transactions', 'transactions.id', '=', 'budget_transaction.transaction_id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transactions.amount', '<', 0) + ->groupBy('budgets.id') + ->groupBy('dateFormatted') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->whereIn('transactions.account_id', $ids) + ->whereIn('budgets.id', $budgetIds) + ->get( + [ + 'budgets.*', + DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), + DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), + ] + ); + $set = $set->sortBy( function (Budget $budget) { return strtolower($budget->name); @@ -348,16 +373,36 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn $return = []; foreach ($set as $budget) { + Log::debug('First set, budget #' . $budget->id . ' (' . $budget->name . ')'); $id = $budget->id; if (!isset($return[$id])) { + Log::debug('$return[$id] is not set, now created.'); $return[$id] = [ 'budget' => $budget, 'entries' => [], ]; } + Log::debug('Add new entry to entries, for ' . $budget->dateFormatted . ' and amount ' . $budget->sumAmount); // store each entry: $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; } + unset($budget); + + // run the second set: + foreach ($secondSet as $entry) { + $id = $entry->id; + // create it if it still does not exist (not really likely) + if (!isset($return[$id])) { + $return[$id] = [ + 'budget' => $entry, + 'entries' => [], + ]; + } + // this one might be filled too: + $startAmount = $return[$id]['entries'][$entry->dateFormatted] ?? '0'; + // store each entry: + $return[$id]['entries'][$entry->dateFormatted] = bcadd($startAmount, $entry->sumAmount); + } return $return; } @@ -453,40 +498,6 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $set; } - /** - * Returns the expenses for this budget grouped per day, with the date - * in "date" (a string, not a Carbon) and the amount in "dailyAmount". - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return Collection - */ - public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts = null): Collection - { - $query = $this->user->budgets() - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.budget_id', '=', 'budgets.id') - ->leftJoin('transaction_journals', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('budgets.id', $budget->id) - ->where('transactions.amount', '<', 0) - ->groupBy('transaction_journals.date') - ->orderBy('transaction_journals.date'); - if (!is_null($accounts) && $accounts->count() > 0) { - $ids = $accounts->pluck('id')->toArray(); - $query->whereIn('transactions.account_id', $ids); - } - $set - = $query->get(['transaction_journals.date', DB::raw('SUM(`transactions`.`amount`) as `dailyAmount`')]); - - return $set; - } - /** * @param Budget $budget * @@ -785,13 +796,14 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget * from all the users accounts. * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return array */ - public function spentPerDay(Budget $budget, Carbon $start, Carbon $end): array + public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array { /** @var Collection $query */ $query = $budget->transactionjournals() @@ -807,6 +819,24 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn $return[$entry['dateFormatted']] = $entry['sum']; } + // also search transactions: + $query = $budget->transactions() + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->where('transactions.amount', '<', 0) + ->before($end) + ->after($start) + ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); + foreach ($query as $newEntry) { + // add to return array. + $date = $newEntry['dateFormatted']; + if (isset($return[$date])) { + $return[$date] = bcadd($newEntry['sum'], $return[$date]); + continue; + } + + $return[$date] = $newEntry['sum']; + } + return $return; } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index b62bc3f208..577b5e18aa 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -131,6 +131,8 @@ interface BudgetRepositoryInterface * @param Carbon $start * @param Carbon $end * + * @deprecated + * * @return array */ public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array; @@ -180,19 +182,6 @@ interface BudgetRepositoryInterface */ public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection; - /** - * Returns the expenses for this budget grouped per day, with the date - * in "date" (a string, not a Carbon) and the amount in "dailyAmount". - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return Collection - */ - public function getExpensesPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts = null) : Collection; - /** * @param Budget $budget * @@ -287,10 +276,11 @@ interface BudgetRepositoryInterface * @param Budget $budget * @param Carbon $start * @param Carbon $end + * @param Collection $accounts * * @return array */ - public function spentPerDay(Budget $budget, Carbon $start, Carbon $end): array; + public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array; /** * @param array $data diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 617c87e9f8..d9ddb99c54 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -81,9 +81,13 @@ class JournalRepository implements JournalRepositoryInterface public function first(): TransactionJournal { $entry = $this->user->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); + if (is_null($entry)) { + Log::debug('Could not find first transaction journal.'); + return new TransactionJournal; } + Log::debug('Found first journal: ', ['date' => $entry->date->format('Y-m-d')]); return $entry; } diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php index 1e4c864b8e..a7c01b30db 100644 --- a/database/seeds/SplitDataSeeder.php +++ b/database/seeds/SplitDataSeeder.php @@ -20,6 +20,7 @@ use Carbon\Carbon; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\Migration\TestData; +use FireflyIII\User; use Illuminate\Database\Seeder; /** @@ -49,101 +50,206 @@ class SplitDataSeeder extends Seeder $user = TestData::createUsers(); // create all kinds of static data: - $assets = [ - [ - 'name' => 'Checking Account', - 'iban' => 'NL11XOLA6707795988', - 'meta' => [ - 'accountRole' => 'defaultAsset', - ], - ], - [ - 'name' => 'Alternate Checking Account', - 'iban' => 'NL40UKBK3619908726', - 'meta' => [ - 'accountRole' => 'defaultAsset', - ], - ], - [ - 'name' => 'Savings Account', - 'iban' => 'NL96DZCO4665940223', - 'meta' => [ - 'accountRole' => 'savingAsset', - ], - ], - [ - 'name' => 'Shared Checking Account', - 'iban' => 'NL81RCQZ7160379858', - 'meta' => [ - 'accountRole' => 'sharedAsset', - ], - ], - ]; + $assets = [['name' => 'Checking Account', 'iban' => 'NL11XOLA6707795988', 'meta' => ['accountRole' => 'defaultAsset']], + ['name' => 'Alternate Checking Account', 'iban' => 'NL40UKBK3619908726', 'meta' => ['accountRole' => 'defaultAsset']], + ['name' => 'Savings Account', 'iban' => 'NL96DZCO4665940223', 'meta' => ['accountRole' => 'savingAsset']], + ['name' => 'Shared Checking Account', 'iban' => 'NL81RCQZ7160379858', 'meta' => ['accountRole' => 'sharedAsset']]]; + + // some asset accounts TestData::createAssetAccounts($user, $assets); + + // budgets, categories and others: TestData::createBudgets($user); TestData::createCategories($user); TestData::createExpenseAccounts($user); TestData::createRevenueAccounts($user); TestData::createPiggybanks($user, 'Savings Account'); + TestData::createBills($user); + // some bills /* * Create splitted expense of 66,- */ - $today = new Carbon; - $today->subDays(2); + $today = new Carbon('2012-03-14'); if (!$skipWithdrawal) { - $journal = TransactionJournal::create( + $this->generateWithdrawals($user); + } + + // create splitted income of 99,- + $today->addDay(); + + if (!$skipDeposit) { + $this->generateDeposits(); + } + // create a splitted transfer of 57,- (19) + // $today->addDay(); + + if (!$skipTransfer) { + $this->generateTransfers(); + } + } + + private function generateDeposits() + { + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 2, // expense + 'transaction_currency_id' => 1, + 'description' => 'Split Income (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), + ] + ); + + // split in 6 transactions (multiple destinations). 22,- each + // source is TestData Checking Account. + // also attach some budgets and stuff. + $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; + $source = TestData::findAccount($user, 'Belastingdienst'); + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); + + $one = Transaction::create( [ - 'user_id' => $user->id, - 'transaction_type_id' => 1, // withdrawal - 'transaction_currency_id' => 1, - 'description' => 'Split Even Expense (journal (50/50))', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-33', + ] ); - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. - $destinations = ['SixtyFive', 'EightyFour']; - $budgets = ['Groceries', 'Car']; - $categories = ['Bills', 'Bills']; - $amounts = [50, 50]; - $source = TestData::findAccount($user, 'Alternate Checking Account'); - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '33', - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amounts[$index] * -1, + ] + ); - ] - ); + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amounts[$index], - - ] - ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); - - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); } - // AND ANOTHER ONE - $today->addDay(); + } + + private function generateTransfers() + { + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 3, // transfer + 'transaction_currency_id' => 1, + 'description' => 'Split Transfer (journal)', + 'completed' => 1, + 'date' => $today->format('Y-m-d'), + ] + ); + + + $source = TestData::findAccount($user, 'Alternate Checking Account'); + $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; + $budgets = ['Groceries', 'Groceries', 'Car']; + $categories = ['Bills', 'Bills', 'Car']; + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); + + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '-19', + + ] + ); + + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => '19', + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } + } + + private function generateWithdrawals(User $user) + { + /* + * TRANSACTION ONE + */ + $date = new Carbon('2012-03-15'); + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, + 'transaction_type_id' => 1, // withdrawal + 'transaction_currency_id' => 1, + 'description' => 'Split Even Expense (journal (50/50))', + 'completed' => 1, + 'date' => $date->format('Y-m-d'), + ] + ); + + // split in 6 transactions (multiple destinations). 22,- each + // source is TestData Checking Account. + // also attach some budgets and stuff. + $destinations = ['SixtyFive', 'EightyFour']; + $budgets = ['Groceries', 'Car']; + $categories = ['Bills', 'Bills']; + $amounts = [50, 50]; + $source = TestData::findAccount($user, 'Alternate Checking Account'); + foreach ($destinations as $index => $dest) { + $bud = $budgets[$index]; + $cat = $categories[$index]; + $destination = TestData::findAccount($user, $dest); + + $one = Transaction::create( + [ + 'account_id' => $source->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $amounts[$index] * -1, + + ] + ); + + $two = Transaction::create( + [ + 'account_id' => $destination->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $amounts[$index], + + ] + ); + + $one->budgets()->save(TestData::findBudget($user, $bud)); + $two->budgets()->save(TestData::findBudget($user, $bud)); + + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } + + /* + * GENERATE TRANSACTION TWO. + */ + + $date = new Carbon; $journal = TransactionJournal::create( [ 'user_id' => $user->id, @@ -151,7 +257,7 @@ class SplitDataSeeder extends Seeder 'transaction_currency_id' => 1, 'description' => 'Split Uneven Expense (journal (15/34/51=100))', 'completed' => 1, - 'date' => $today->format('Y-m-d'), + 'date' => $date->format('Y-m-d'), ] ); @@ -192,108 +298,5 @@ class SplitDataSeeder extends Seeder $one->categories()->save(TestData::findCategory($user, $cat)); $two->categories()->save(TestData::findCategory($user, $cat)); } - - // create splitted income of 99,- - $today->addDay(); - - if (!$skipDeposit) { - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 2, // expense - 'transaction_currency_id' => 1, - 'description' => 'Split Income (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] - ); - - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. - $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; - $source = TestData::findAccount($user, 'Belastingdienst'); - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-33', - - ] - ); - - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '33', - - ] - ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); - - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - } - // create a splitted transfer of 57,- (19) - $today->addDay(); - - if (!$skipTransfer) { - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 3, // transfer - 'transaction_currency_id' => 1, - 'description' => 'Split Transfer (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] - ); - - - $source = TestData::findAccount($user, 'Alternate Checking Account'); - $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-19', - - ] - ); - - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '19', - - ] - ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); - - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - } } } From adf669147034ec45cb8541bcd8d580754d1b367a Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 5 May 2016 22:03:35 +0200 Subject: [PATCH 086/206] This breaks everything budget-related. --- app/Helpers/Report/BalanceReportHelper.php | 4 +- app/Helpers/Report/BudgetReportHelper.php | 6 +- app/Http/Controllers/BudgetController.php | 25 +- .../Controllers/Chart/BudgetController.php | 631 ++++---- .../Controllers/Popup/ReportController.php | 11 +- app/Repositories/Budget/BudgetRepository.php | 1353 ++++++++--------- .../Budget/BudgetRepositoryInterface.php | 412 +++-- 7 files changed, 1219 insertions(+), 1223 deletions(-) diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index 5c9f08a90a..472ab2dc88 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -68,8 +68,8 @@ class BalanceReportHelper implements BalanceReportHelperInterface // build a balance header: $header = new BalanceHeader; - $budgets = $this->budgetRepository->getBudgetsAndLimitsInRange($start, $end); - $spentData = $this->budgetRepository->spentPerBudgetPerAccount($budgets, $accounts, $start, $end); + $budgets = new Collection;// $this->budgetRepository->getBudgetsAndLimitsInRange($start, $end); // TODO BUDGET getBudgets + $spentData = new Collection; // $this->budgetRepository->spentPerBudgetPerAccount($budgets, $accounts, $start, $end); TODO BUDGET journalsInPeriod foreach ($accounts as $account) { $header->addAccount($account); } diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 8a9de7eaec..04124aace4 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -41,7 +41,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface $repository = app(BudgetRepositoryInterface::class); $set = $repository->getBudgets(); $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end); - $allTotalSpent = $repository->spentAllPerDayForAccounts($accounts, $start, $end); + $allTotalSpent = '0'; //$repository->spentAllPerDayForAccounts($accounts, $start, $end);// TODO BUDGET MASSIVELY STUPID SPECIFIC METHOD foreach ($set as $budget) { @@ -98,7 +98,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface } // stuff outside of budgets: - $noBudget = $repository->getWithoutBudgetSum($accounts, $start, $end); + $noBudget = '0'; //$repository->getWithoutBudgetSum($accounts, $start, $end); // TODO BUDGET journalsInPeriodWithoutBudget $budgetLine = new BudgetLine; $budgetLine->setOverspent($noBudget); $budgetLine->setSpent($noBudget); @@ -124,7 +124,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface $set = new Collection; /** @var Budget $budget */ foreach ($budgets as $budget) { - $expenses = $repository->spentPerDay($budget, $start, $end, $accounts); + $expenses = [0]; // $repository->spentPerDay($budget, $start, $end, $accounts); // TODO BUDGET spentInPeriod $total = strval(array_sum($expenses)); if (bccomp($total, '0') === -1) { $set->push($budget); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 0fb3acae7c..0436135375 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -10,6 +10,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Input; use Navigation; @@ -177,14 +178,16 @@ class BudgetController extends Controller /** * Do some cleanup: */ - $repository->cleanupBudgets(); + // $repository->cleanupBudgets(); // loop the budgets: /** @var Budget $budget */ foreach ($budgets as $budget) { - $budget->spent = $repository->balanceInPeriod($budget, $start, $end, $accounts); - $budget->currentRep = $repository->getCurrentRepetition($budget, $repeatFreq, $start, $end); - $budget->otherRepetitions = $repository->getValidRepetitions($budget, $start, $end, $budget->currentRep); + $budget->spent = '0';//$repository->balanceInPeriod($budget, $start, $end, $accounts); // TODO BUDGET spentInPeriod + $budget->currentRep = new LimitRepetition( + ); // $repository->getCurrentRepetition($budget, $repeatFreq, $start, $end); // TODO BUDGET getBudgetLimitRepetitions + $budget->otherRepetitions = new Collection( + );//$repository->getValidRepetitions($budget, $start, $end, $budget->currentRep); // TODO BUDGET getBudgetLimitRepetitions if (!is_null($budget->currentRep->id)) { $budgeted = bcadd($budgeted, $budget->currentRep->amount); } @@ -220,7 +223,7 @@ class BudgetController extends Controller $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); $pageSize = Preferences::get('transactionPageSize', 50)->data; - $list = $repository->getWithoutBudget($start, $end, $page, $pageSize); + $list = new LengthAwarePaginator([], 0, $pageSize); // $repository->getWithoutBudget($start, $end, $page, $pageSize); // TODO BUDGET journalsInPeriodWithoutBudget $subTitle = trans( 'firefly.without_budget_between', ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] @@ -258,13 +261,15 @@ class BudgetController extends Controller public function show(BudgetRepositoryInterface $repository, Budget $budget) { $pageSize = Preferences::get('transactionPageSize', 50)->data; - $journals = $repository->getJournals($budget, new LimitRepetition, $pageSize); - $start = $repository->firstActivity($budget); + $journals = new LengthAwarePaginator( + [], 0, $pageSize + ); //$repository->getJournals($budget, new LimitRepetition, $pageSize); // TODO BUDGET journalsInPeriod + $start = new Carbon; //$repository->firstActivity($budget); // TODO BUDGET getOldestJournal $end = new Carbon; $set = $budget->limitrepetitions()->orderBy('startdate', 'DESC')->get(); $subTitle = e($budget->name); $journals->setPath('/budgets/show/' . $budget->id); - $spentArray = $repository->spentPerDay($budget, $start, $end, new Collection); + $spentArray = []; //$repository->spentPerDay($budget, $start, $end, new Collection); // TODO BUDGET spentInPeriod $limits = new Collection(); /** @var LimitRepetition $entry */ @@ -292,13 +297,13 @@ class BudgetController extends Controller } $pageSize = Preferences::get('transactionPageSize', 50)->data; - $journals = $repository->getJournals($budget, $repetition, $pageSize); + $journals = new LengthAwarePaginator([], 0, $pageSize); // $repository->getJournals($budget, $repetition, $pageSize); // TODO BUDGET journalsInPeriod $start = $repetition->startdate; $end = $repetition->enddate; $set = new Collection([$repetition]); $subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]); $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id); - $spentArray = $repository->spentPerDay($budget, $start, $end, new Collection); + $spentArray = []; //$repository->spentPerDay($budget, $start, $end, new Collection); // TODO BUDGET spentInPeriod $limits = new Collection(); /** @var LimitRepetition $entry */ diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 66814507ed..f2ed25a9b3 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -8,14 +8,9 @@ use FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; -use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; -use Navigation; -use Preferences; -use Response; /** * Class BudgetController @@ -48,45 +43,46 @@ class BudgetController extends Controller */ public function budget(BudgetRepositoryInterface $repository, Budget $budget) { - - // dates and times - $first = $repository->getFirstBudgetLimitDate($budget); - $range = Preferences::get('viewRange', '1M')->data; - $last = session('end', new Carbon); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($first); - $cache->addProperty($last); - $cache->addProperty('budget'); - if ($cache->has()) { - - //return Response::json($cache->get()); - } - - $final = clone $last; - $final->addYears(2); - $last = Navigation::endOfX($last, $range, $final); - $entries = new Collection; - // get all expenses: - $spentArray = $repository->spentPerDay($budget, $first, $last, new Collection); - - while ($first < $last) { - - // periodspecific dates: - $currentStart = Navigation::startOfPeriod($first, $range); - $currentEnd = Navigation::endOfPeriod($first, $range); - $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray); - $entry = [$first, ($spent * -1)]; - - $entries->push($entry); - $first = Navigation::addPeriod($first, $range, 0); - } - - $data = $this->generator->budgetLimit($entries, 'month'); - $cache->store($data); - - return Response::json($data); + /** + * // dates and times + * $first = $repository->getFirstBudgetLimitDate($budget); + * $range = Preferences::get('viewRange', '1M')->data; + * $last = session('end', new Carbon); + * + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($first); + * $cache->addProperty($last); + * $cache->addProperty('budget'); + * if ($cache->has()) { + * + * //return Response::json($cache->get()); + * } + * + * $final = clone $last; + * $final->addYears(2); + * $last = Navigation::endOfX($last, $range, $final); + * $entries = new Collection; + * // get all expenses: + * $spentArray = $repository->spentPerDay($budget, $first, $last, new Collection); + * + * while ($first < $last) { + * + * // periodspecific dates: + * $currentStart = Navigation::startOfPeriod($first, $range); + * $currentEnd = Navigation::endOfPeriod($first, $range); + * $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray); + * $entry = [$first, ($spent * -1)]; + * + * $entries->push($entry); + * $first = Navigation::addPeriod($first, $range, 0); + * } + * + * $data = $this->generator->budgetLimit($entries, 'month'); + * $cache->store($data); + * + * return Response::json($data); + * **/ } /** @@ -100,42 +96,42 @@ class BudgetController extends Controller */ public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition) { - $start = clone $repetition->startdate; - $end = $repetition->enddate; - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('budget'); - $cache->addProperty('limit'); - $cache->addProperty($budget->id); - $cache->addProperty($repetition->id); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $set = $repository->spentPerDay($budget, $start, $end, new Collection); - $entries = new Collection; - $amount = $repetition->amount; - - // get sum (har har)! - while ($start <= $end) { - $formatted = $start->format('Y-m-d'); - $sum = $set[$formatted] ?? '0'; - - /* - * Sum of expenses on this day: - */ - $amount = round(bcadd(strval($amount), $sum), 2); - $entries->push([clone $start, $amount]); - $start->addDay(); - } - - $data = $this->generator->budgetLimit($entries, 'monthAndDay'); - $cache->store($data); - - return Response::json($data); + /** + * $start = clone $repetition->startdate; + * $end = $repetition->enddate; + * + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty('budget'); + * $cache->addProperty('limit'); + * $cache->addProperty($budget->id); + * $cache->addProperty($repetition->id); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * $set = $repository->spentPerDay($budget, $start, $end, new Collection); + * $entries = new Collection; + * $amount = $repetition->amount; + * + * // get sum (har har)! + * while ($start <= $end) { + * $formatted = $start->format('Y-m-d'); + * $sum = $set[$formatted] ?? '0'; + * + * // Sum of expenses on this day: + * $amount = round(bcadd(strval($amount), $sum), 2); + * $entries->push([clone $start, $amount]); + * $start->addDay(); + * } + * + * $data = $this->generator->budgetLimit($entries, 'monthAndDay'); + * $cache->store($data); + * + * return Response::json($data); + **/ } @@ -150,75 +146,77 @@ class BudgetController extends Controller */ public function frontpage(BudgetRepositoryInterface $repository, ARI $accountRepository) { - $start = session('start', Carbon::now()->startOfMonth()); - $end = session('end', Carbon::now()->endOfMonth()); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('budget'); - $cache->addProperty('all'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); - $allEntries = new Collection; - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); - $format = strval(trans('config.month_and_day')); - - - /** @var Budget $budget */ - foreach ($budgets as $budget) { - // we already have amount, startdate and enddate. - // if this "is" a limit repetition (as opposed to a budget without one entirely) - // depends on whether startdate and enddate are null. - $name = $budget->name; - if (is_null($budget->startdate) && is_null($budget->enddate)) { - $currentStart = clone $start; - $currentEnd = clone $end; - $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); - $amount = '0'; - $left = '0'; - $spent = $expenses; - $overspent = '0'; - } else { - - // update the display name if the range - // of the limit repetition does not match - // the session's range (for clarity). - if ( - ($start->format('Y-m-d') != $budget->startdate->format('Y-m-d')) - || ($end->format('Y-m-d') != $budget->enddate->format('Y-m-d')) - ) { - $name .= ' ' . trans( - 'firefly.between_dates', - [ - 'start' => $budget->startdate->formatLocalized($format), - 'end' => $budget->startdate->formatLocalized($format), - ] - ); - } - $currentStart = clone $budget->startdate; - $currentEnd = clone $budget->enddate; - $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); - $amount = $budget->amount; - // smaller than 1 means spent MORE than budget allows. - $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? '0' : bcadd($budget->amount, $expenses); - $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses; - $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : '0'; - } - - $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]); - } - - $noBudgetExpenses = $repository->getWithoutBudgetSum($accounts, $start, $end); - $allEntries->push([trans('firefly.no_budget'), '0', '0', $noBudgetExpenses, '0', '0']); - $data = $this->generator->frontpage($allEntries); - $cache->store($data); - - return Response::json($data); + /** + * $start = session('start', Carbon::now()->startOfMonth()); + * $end = session('end', Carbon::now()->endOfMonth()); + * + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty('budget'); + * $cache->addProperty('all'); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); + * $allEntries = new Collection; + * $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + * $format = strval(trans('config.month_and_day')); + * + * + * // @var Budget $budget + * foreach ($budgets as $budget) { + * // we already have amount, startdate and enddate. + * // if this "is" a limit repetition (as opposed to a budget without one entirely) + * // depends on whether startdate and enddate are null. + * $name = $budget->name; + * if (is_null($budget->startdate) && is_null($budget->enddate)) { + * $currentStart = clone $start; + * $currentEnd = clone $end; + * $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); + * $amount = '0'; + * $left = '0'; + * $spent = $expenses; + * $overspent = '0'; + * } else { + * + * // update the display name if the range + * // of the limit repetition does not match + * // the session's range (for clarity). + * if ( + * ($start->format('Y-m-d') != $budget->startdate->format('Y-m-d')) + * || ($end->format('Y-m-d') != $budget->enddate->format('Y-m-d')) + * ) { + * $name .= ' ' . trans( + * 'firefly.between_dates', + * [ + * 'start' => $budget->startdate->formatLocalized($format), + * 'end' => $budget->startdate->formatLocalized($format), + * ] + * ); + * } + * $currentStart = clone $budget->startdate; + * $currentEnd = clone $budget->enddate; + * $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); + * $amount = $budget->amount; + * // smaller than 1 means spent MORE than budget allows. + * $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? '0' : bcadd($budget->amount, $expenses); + * $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses; + * $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : '0'; + * } + * + * $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]); + * } + * + * $noBudgetExpenses = $repository->getWithoutBudgetSum($accounts, $start, $end); + * $allEntries->push([trans('firefly.no_budget'), '0', '0', $noBudgetExpenses, '0', '0']); + * $data = $this->generator->frontpage($allEntries); + * $cache->store($data); + * + * return Response::json($data); + **/ } /** @@ -235,73 +233,72 @@ class BudgetController extends Controller */ public function multiYear(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets) { - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($reportType); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($accounts); - $cache->addProperty($budgets); - $cache->addProperty('multiYearBudget'); - - if ($cache->has()) { - return Response::json($cache->get()); - } - - /* - * Get the budgeted amounts for each budgets in each year. - */ - $budgetedSet = $repository->getBudgetedPerYear($budgets, $start, $end); - $budgetedArray = []; - /** @var Budget $entry */ - foreach ($budgetedSet as $entry) { - $budgetedArray[$entry->id][$entry->dateFormatted] = $entry->budgeted; - } - - $set = $repository->getBudgetsAndExpensesPerYear($budgets, $accounts, $start, $end); - $entries = new Collection; - // go by budget, not by year. - /** @var Budget $budget */ - foreach ($budgets as $budget) { - $entry = ['name' => '', 'spent' => [], 'budgeted' => []]; - $id = $budget->id; - $currentStart = clone $start; - while ($currentStart < $end) { - // fix the date: - $currentEnd = clone $currentStart; - $currentEnd->endOfYear(); - - // basic information: - $year = $currentStart->year; - $entry['name'] = $budget->name ?? (string)trans('firefly.no_budget'); - $spent = 0; - // this might be a good moment to collect no budget stuff. - if (is_null($budget->id)) { - // get without budget sum in range: - $spent = $repository->getWithoutBudgetSum($accounts, $currentStart, $currentEnd) * -1; - } else { - if (isset($set[$id]['entries'][$year])) { - $spent = $set[$id]['entries'][$year] * -1; - } - } - - $budgeted = $budgetedArray[$id][$year] ?? '0'; - $entry['spent'][$year] = $spent; - $entry['budgeted'][$year] = round($budgeted, 2); - - - // jump to next year. - $currentStart = clone $currentEnd; - $currentStart->addDay(); - } - $entries->push($entry); - } - // generate chart with data: - $data = $this->generator->multiYear($entries); - $cache->store($data); - - return Response::json($data); - + /** + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($reportType); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($accounts); + * $cache->addProperty($budgets); + * $cache->addProperty('multiYearBudget'); + * + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * // Get the budgeted amounts for each budgets in each year. + * $budgetedSet = $repository->getBudgetedPerYear($budgets, $start, $end); + * $budgetedArray = []; + * // @var Budget $entry + * foreach ($budgetedSet as $entry) { + * $budgetedArray[$entry->id][$entry->dateFormatted] = $entry->budgeted; + * } + * + * $set = $repository->getBudgetsAndExpensesPerYear($budgets, $accounts, $start, $end); + * $entries = new Collection; + * // go by budget, not by year. + * // @var Budget $budget + * foreach ($budgets as $budget) { + * $entry = ['name' => '', 'spent' => [], 'budgeted' => []]; + * $id = $budget->id; + * $currentStart = clone $start; + * while ($currentStart < $end) { + * // fix the date: + * $currentEnd = clone $currentStart; + * $currentEnd->endOfYear(); + * + * // basic information: + * $year = $currentStart->year; + * $entry['name'] = $budget->name ?? (string)trans('firefly.no_budget'); + * $spent = 0; + * // this might be a good moment to collect no budget stuff. + * if (is_null($budget->id)) { + * // get without budget sum in range: + * $spent = $repository->getWithoutBudgetSum($accounts, $currentStart, $currentEnd) * -1; + * } else { + * if (isset($set[$id]['entries'][$year])) { + * $spent = $set[$id]['entries'][$year] * -1; + * } + * } + * + * $budgeted = $budgetedArray[$id][$year] ?? '0'; + * $entry['spent'][$year] = $spent; + * $entry['budgeted'][$year] = round($budgeted, 2); + * + * + * // jump to next year. + * $currentStart = clone $currentEnd; + * $currentStart->addDay(); + * } + * $entries->push($entry); + * } + * // generate chart with data: + * $data = $this->generator->multiYear($entries); + * $cache->store($data); + * + * return Response::json($data); + **/ } /** @@ -315,58 +312,60 @@ class BudgetController extends Controller */ public function period(Budget $budget, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - $cache->addProperty($budget->id); - $cache->addProperty('budget'); - $cache->addProperty('period'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - // loop over period, add by users range: - $current = clone $start; - $viewRange = Preferences::get('viewRange', '1M')->data; - $set = new Collection; - while ($current < $end) { - $currentStart = clone $current; - $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); - - // get all budget limits and their repetitions. - $reps = $repository->getAllBudgetLimitRepetitions($currentStart, $currentEnd, $budget); - $budgeted = $reps->sum('amount'); - $perBudget = $repository->spentPerBudgetPerAccount(new Collection([$budget]), $accounts, $currentStart, $currentEnd); - // includes null, so filter! - $perBudget = $perBudget->filter( - function (TransactionJournal $journal) use ($budget) { - if (intval($journal->budget_id) === $budget->id) { - return $journal; - } - } - ); - - - $spent = $perBudget->sum('spent'); - - $entry = [ - 'date' => clone $currentStart, - 'budgeted' => $budgeted, - 'spent' => $spent, - ]; - $set->push($entry); - $currentEnd->addDay(); - $current = clone $currentEnd; - } - $data = $this->generator->period($set, $viewRange); - $cache->store($data); - - return Response::json($data); + /** + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($reportType); + * $cache->addProperty($accounts); + * $cache->addProperty($budget->id); + * $cache->addProperty('budget'); + * $cache->addProperty('period'); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * // @var BudgetRepositoryInterface $repository + * $repository = app(BudgetRepositoryInterface::class); + * // loop over period, add by users range: + * $current = clone $start; + * $viewRange = Preferences::get('viewRange', '1M')->data; + * $set = new Collection; + * while ($current < $end) { + * $currentStart = clone $current; + * $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); + * + * // get all budget limits and their repetitions. + * $reps = $repository->getAllBudgetLimitRepetitions($currentStart, $currentEnd, $budget); + * $budgeted = $reps->sum('amount'); + * $perBudget = $repository->spentPerBudgetPerAccount(new Collection([$budget]), $accounts, $currentStart, $currentEnd); + * // includes null, so filter! + * $perBudget = $perBudget->filter( + * function (TransactionJournal $journal) use ($budget) { + * if (intval($journal->budget_id) === $budget->id) { + * return $journal; + * } + * } + * ); + * + * + * $spent = $perBudget->sum('spent'); + * + * $entry = [ + * 'date' => clone $currentStart, + * 'budgeted' => $budgeted, + * 'spent' => $spent, + * ]; + * $set->push($entry); + * $currentEnd->addDay(); + * $current = clone $currentEnd; + * } + * $data = $this->generator->period($set, $viewRange); + * $cache->store($data); + * + * return Response::json($data); + */ } @@ -382,52 +381,54 @@ class BudgetController extends Controller */ public function year(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - $cache->addProperty('budget'); - $cache->addProperty('year'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $budgetInformation = $repository->getBudgetsAndExpensesPerMonth($accounts, $start, $end); - $budgets = new Collection; - $entries = new Collection; - - /** @var array $row */ - foreach ($budgetInformation as $row) { - $budgets->push($row['budget']); - } - while ($start < $end) { - // month is the current end of the period: - $month = clone $start; - $month->endOfMonth(); - $row = [clone $start]; - $dateFormatted = $start->format('Y-m'); - - // each budget, check if there is an entry for this month: - /** @var array $row */ - foreach ($budgetInformation as $budgetRow) { - $spent = 0; // nothing spent. - if (isset($budgetRow['entries'][$dateFormatted])) { - $spent = $budgetRow['entries'][$dateFormatted] * -1; // to fit array - } - $row[] = $spent; - } - - // add "no budget" thing. - $row[] = round(bcmul($repository->getWithoutBudgetSum($accounts, $start, $month), '-1'), 4); - - $entries->push($row); - $start->endOfMonth()->addDay(); - } - $data = $this->generator->year($budgets, $entries); - $cache->store($data); - - return Response::json($data); + /** + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($reportType); + * $cache->addProperty($accounts); + * $cache->addProperty('budget'); + * $cache->addProperty('year'); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * $budgetInformation = $repository->getBudgetsAndExpensesPerMonth($accounts, $start, $end); + * $budgets = new Collection; + * $entries = new Collection; + * + * // @var array $row + * foreach ($budgetInformation as $row) { + * $budgets->push($row['budget']); + * } + * while ($start < $end) { + * // month is the current end of the period: + * $month = clone $start; + * $month->endOfMonth(); + * $row = [clone $start]; + * $dateFormatted = $start->format('Y-m'); + * + * // each budget, check if there is an entry for this month: + * // @var array $row + * foreach ($budgetInformation as $budgetRow) { + * $spent = 0; // nothing spent. + * if (isset($budgetRow['entries'][$dateFormatted])) { + * $spent = $budgetRow['entries'][$dateFormatted] * -1; // to fit array + * } + * $row[] = $spent; + * } + * + * // add "no budget" thing. + * $row[] = round(bcmul($repository->getWithoutBudgetSum($accounts, $start, $month), '-1'), 4); + * + * $entries->push($row); + * $start->endOfMonth()->addDay(); + * } + * $data = $this->generator->year($budgets, $entries); + * $cache->store($data); + * + * return Response::json($data); + */ } } diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index 13edc40765..048043c060 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -22,6 +22,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface; use FireflyIII\Support\Binder\AccountList; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use InvalidArgumentException; use Response; use View; @@ -93,15 +94,15 @@ class ReportController extends Controller switch (true) { case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)): - $journals = $budgetRepository->expensesSplit($budget, $account, $attributes['startDate'], $attributes['endDate']); + $journals = new Collection;// $budgetRepository->expensesSplit($budget, $account, $attributes['startDate'], $attributes['endDate']);// TODO BUDGET journalsInPeriod break; case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)): $budget->name = strval(trans('firefly.no_budget')); - $journals = $budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $journals = new Collection;// $budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriodWithoutBudget break; case ($role === BalanceLine::ROLE_DIFFROLE): // journals no budget, not corrected by a tag. - $journals = $budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $journals = new Collection; //$budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriodWithoutBudget $budget->name = strval(trans('firefly.leftUnbalanced')); $journals = $journals->filter( function (TransactionJournal $journal) { @@ -137,10 +138,10 @@ class ReportController extends Controller $repository = app(BudgetRepositoryInterface::class); $budget = $repository->find(intval($attributes['budgetId'])); if (is_null($budget->id)) { - $journals = $repository->getWithoutBudgetForAccounts($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $journals = new Collection;// $repository->getWithoutBudgetForAccounts($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriodWithoutBudget } else { // get all expenses in budget in period: - $journals = $repository->getExpenses($budget, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $journals = new Collection; //$repository->getExpenses($budget, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriod } $view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render(); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index c097f4b230..f55774f5c8 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -7,7 +7,6 @@ use Carbon\Carbon; use DB; use FireflyIII\Events\BudgetLimitStored; use FireflyIII\Events\BudgetLimitUpdated; -use FireflyIII\Models\Account; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; @@ -21,7 +20,6 @@ use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Input; -use Log; /** * Class BudgetRepository @@ -43,30 +41,30 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn $this->user = $user; } - /** - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return string - */ - public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): string - { - return $this->commonBalanceInPeriod($budget, $start, $end, $accounts); - } + // /** + // * @param Budget $budget + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return string + // */ + // public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): string + // { + // return $this->commonBalanceInPeriod($budget, $start, $end, $accounts); + // } - /** - * @return bool - */ - public function cleanupBudgets(): bool - { - // delete limits with amount 0: - BudgetLimit::where('amount', 0)->delete(); - - return true; - - } + // /** + // * @return bool + // */ + // public function cleanupBudgets(): bool + // { + // // delete limits with amount 0: + // BudgetLimit::where('amount', 0)->delete(); + // + // return true; + // + // } /** * @param Budget $budget @@ -80,22 +78,22 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return true; } - /** - * @param Budget $budget - * @param Account $account - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection - { - return $budget->transactionjournals()->expanded() - ->before($end) - ->after($start) - ->where('source_account.id', $account->id) - ->get(TransactionJournal::queryFields()); - } + // /** + // * @param Budget $budget + // * @param Account $account + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection + // { + // return $budget->transactionjournals()->expanded() + // ->before($end) + // ->after($start) + // ->where('source_account.id', $account->id) + // ->get(TransactionJournal::queryFields()); + // } /** * Find a budget. @@ -114,20 +112,20 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $budget; } - /** - * @param Budget $budget - * - * @return Carbon - */ - public function firstActivity(Budget $budget): Carbon - { - $first = $budget->transactionjournals()->orderBy('date', 'ASC')->first(); - if ($first) { - return $first->date; - } - - return new Carbon; - } + // /** + // * @param Budget $budget + // * + // * @return Carbon + // */ + // public function firstActivity(Budget $budget): Carbon + // { + // $first = $budget->transactionjournals()->orderBy('date', 'ASC')->first(); + // if ($first) { + // return $first->date; + // } + // + // return new Carbon; + // } /** * @return Collection @@ -149,11 +147,10 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn /** * @param Carbon $start * @param Carbon $end - * @param Budget $budget * * @return Collection */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end, Budget $budget = null): Collection + public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection { $query = LimitRepetition:: leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') @@ -162,70 +159,66 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) ->where('budgets.user_id', $this->user->id); - if (!is_null($budget)) { - $query->where('budgets.id', $budget->id); - } - $set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']); return $set; } - /** - * @param Account $account - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return Collection - */ - public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); + // /** + // * @param Account $account + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return Collection + // */ + // public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // $ids = $accounts->pluck('id')->toArray(); + // + // return $this->user + // ->transactionjournals() + // ->expanded() + // ->where('source_account.id', $account->id) + // ->whereNotIn('destination_account.id', $ids) + // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->whereNull('budget_transaction_journal.id') + // ->before($end) + // ->after($start) + // ->get(TransactionJournal::queryFields()); + // } - return $this->user - ->transactionjournals() - ->expanded() - ->where('source_account.id', $account->id) - ->whereNotIn('destination_account.id', $ids) - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('budget_transaction_journal.id') - ->before($end) - ->after($start) - ->get(TransactionJournal::queryFields()); - } - - /** - * Get the budgeted amounts for each budgets in each year. - * - * @param Collection $budgets - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection - { - $budgetIds = $budgets->pluck('id')->toArray(); - - $set = $this->user->budgets() - ->leftJoin('budget_limits', 'budgets.id', '=', 'budget_limits.budget_id') - ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')) - ->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d')) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->whereIn('budgets.id', $budgetIds) - ->get( - [ - 'budgets.*', - DB::raw('DATE_FORMAT(`limit_repetitions`.`startdate`,"%Y") as `dateFormatted`'), - DB::raw('SUM(`limit_repetitions`.`amount`) as `budgeted`'), - ] - ); - - return $set; - } + // /** + // * Get the budgeted amounts for each budgets in each year. + // * + // * @param Collection $budgets + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection + // { + // $budgetIds = $budgets->pluck('id')->toArray(); + // + // $set = $this->user->budgets() + // ->leftJoin('budget_limits', 'budgets.id', '=', 'budget_limits.budget_id') + // ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + // ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')) + // ->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d')) + // ->groupBy('budgets.id') + // ->groupBy('dateFormatted') + // ->whereIn('budgets.id', $budgetIds) + // ->get( + // [ + // 'budgets.*', + // DB::raw('DATE_FORMAT(`limit_repetitions`.`startdate`,"%Y") as `dateFormatted`'), + // DB::raw('SUM(`limit_repetitions`.`amount`) as `budgeted`'), + // ] + // ); + // + // return $set; + // } /** * @return Collection @@ -244,274 +237,274 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $set; } - /** - * Returns an array with every budget in it and the expenses for each budget - * per month. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array - { - $ids = $accounts->pluck('id')->toArray(); + // /** + // * Returns an array with every budget in it and the expenses for each budget + // * per month. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return array + // */ + // public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array + // { + // $ids = $accounts->pluck('id')->toArray(); + // + // /** @var Collection $set */ + // $set = $this->user->budgets() + // ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + // ->leftJoin( + // 'transactions', function (JoinClause $join) { + // $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + // } + // ) + // ->groupBy('budgets.id') + // ->groupBy('dateFormatted') + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + // ->whereIn('transactions.account_id', $ids) + // ->get( + // [ + // 'budgets.*', + // DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'), + // DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), + // ] + // ); + // + // $set = $set->sortBy( + // function (Budget $budget) { + // return strtolower($budget->name); + // } + // ); + // + // $return = []; + // foreach ($set as $budget) { + // $id = $budget->id; + // if (!isset($return[$id])) { + // $return[$id] = [ + // 'budget' => $budget, + // 'entries' => [], + // ]; + // } + // // store each entry: + // $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; + // } + // + // return $return; + // } - /** @var Collection $set */ - $set = $this->user->budgets() - ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereIn('transactions.account_id', $ids) - ->get( - [ - 'budgets.*', - DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'), - DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - ] - ); + // /** + // * Returns an array with every budget in it and the expenses for each budget + // * per year for. + // * + // * @param Collection $budgets + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @deprecated + // * + // * @return array + // */ + // public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array + // { + // // get budgets, + // $ids = $accounts->pluck('id')->toArray(); + // $budgetIds = $budgets->pluck('id')->toArray(); + // + // /** @var Collection $set */ + // $set = $this->user->budgets() + // ->join('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + // ->leftJoin( + // 'transactions', function (JoinClause $join) { + // $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + // } + // ) + // ->groupBy('budgets.id') + // ->groupBy('dateFormatted') + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + // ->whereIn('transactions.account_id', $ids) + // ->whereIn('budgets.id', $budgetIds) + // ->get( + // [ + // 'budgets.*', + // DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), + // DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), + // ] + // ); + // + // // run it again, for transactions this time. + // /** @var Collection $secondSet */ + // $secondSet = $this->user->budgets() + // ->join('budget_transaction', 'budgets.id', '=', 'budget_transaction.budget_id') + // ->leftJoin('transactions', 'transactions.id', '=', 'budget_transaction.transaction_id') + // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + // ->where('transactions.amount', '<', 0) + // ->groupBy('budgets.id') + // ->groupBy('dateFormatted') + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + // ->whereIn('transactions.account_id', $ids) + // ->whereIn('budgets.id', $budgetIds) + // ->get( + // [ + // 'budgets.*', + // DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), + // DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), + // ] + // ); + // + // $set = $set->sortBy( + // function (Budget $budget) { + // return strtolower($budget->name); + // } + // ); + // + // $return = []; + // foreach ($set as $budget) { + // Log::debug('First set, budget #' . $budget->id . ' (' . $budget->name . ')'); + // $id = $budget->id; + // if (!isset($return[$id])) { + // Log::debug('$return[$id] is not set, now created.'); + // $return[$id] = [ + // 'budget' => $budget, + // 'entries' => [], + // ]; + // } + // Log::debug('Add new entry to entries, for ' . $budget->dateFormatted . ' and amount ' . $budget->sumAmount); + // // store each entry: + // $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; + // } + // unset($budget); + // + // // run the second set: + // foreach ($secondSet as $entry) { + // $id = $entry->id; + // // create it if it still does not exist (not really likely) + // if (!isset($return[$id])) { + // $return[$id] = [ + // 'budget' => $entry, + // 'entries' => [], + // ]; + // } + // // this one might be filled too: + // $startAmount = $return[$id]['entries'][$entry->dateFormatted] ?? '0'; + // // store each entry: + // $return[$id]['entries'][$entry->dateFormatted] = bcadd($startAmount, $entry->sumAmount); + // } + // + // return $return; + // } - $set = $set->sortBy( - function (Budget $budget) { - return strtolower($budget->name); - } - ); + // /** + // * Returns a list of budgets, budget limits and limit repetitions + // * (doubling any of them in a left join) + // * + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection + // { + // /** @var Collection $set */ + // $set = $this->user + // ->budgets() + // ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') + // ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + // ->where( + // function (Builder $query) use ($start, $end) { + // $query->where( + // function (Builder $query) use ($start, $end) { + // $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')); + // $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d')); + // } + // ); + // $query->orWhere( + // function (Builder $query) { + // $query->whereNull('limit_repetitions.startdate'); + // $query->whereNull('limit_repetitions.enddate'); + // } + // ); + // } + // ) + // ->orderBy('budgets.id', 'budget_limits.startdate', 'limit_repetitions.enddate') + // ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']); + // + // $set = $set->sortBy( + // function (Budget $budget) { + // return strtolower($budget->name); + // } + // ); + // + // return $set; + // + // } - $return = []; - foreach ($set as $budget) { - $id = $budget->id; - if (!isset($return[$id])) { - $return[$id] = [ - 'budget' => $budget, - 'entries' => [], - ]; - } - // store each entry: - $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; - } +// /** +// * @param Budget $budget +// * @param string $repeatFreq +// * @param Carbon $start +// * @param Carbon $end +// * +// * @return LimitRepetition +// */ +// public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition +// { +// $data = $budget->limitrepetitions() +// ->where('budget_limits.repeat_freq', $repeatFreq) +// ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) +// ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00')) +// ->first(['limit_repetitions.*']); +// if (is_null($data)) { +// return new LimitRepetition; +// } +// +// return $data; +// } - return $return; - } +// /** +// * Returns all expenses for the given budget and the given accounts, in the given period. +// * +// * @param Budget $budget +// * @param Collection $accounts +// * @param Carbon $start +// * @param Carbon $end +// * +// * @return Collection +// */ +// public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection +// { +// $ids = $accounts->pluck('id')->toArray(); +// $set = $budget->transactionjournals() +// ->before($end) +// ->after($start) +// ->expanded() +// ->where('transaction_types.type', TransactionType::WITHDRAWAL) +// ->whereIn('source_account.id', $ids) +// ->get(TransactionJournal::queryFields()); +// +// return $set; +// } - /** - * Returns an array with every budget in it and the expenses for each budget - * per year for. - * - * @param Collection $budgets - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @deprecated - * - * @return array - */ - public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array - { - // get budgets, - $ids = $accounts->pluck('id')->toArray(); - $budgetIds = $budgets->pluck('id')->toArray(); - - /** @var Collection $set */ - $set = $this->user->budgets() - ->join('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereIn('transactions.account_id', $ids) - ->whereIn('budgets.id', $budgetIds) - ->get( - [ - 'budgets.*', - DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), - DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - ] - ); - - // run it again, for transactions this time. - /** @var Collection $secondSet */ - $secondSet = $this->user->budgets() - ->join('budget_transaction', 'budgets.id', '=', 'budget_transaction.budget_id') - ->leftJoin('transactions', 'transactions.id', '=', 'budget_transaction.transaction_id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transactions.amount', '<', 0) - ->groupBy('budgets.id') - ->groupBy('dateFormatted') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->whereIn('transactions.account_id', $ids) - ->whereIn('budgets.id', $budgetIds) - ->get( - [ - 'budgets.*', - DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), - DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - ] - ); - - $set = $set->sortBy( - function (Budget $budget) { - return strtolower($budget->name); - } - ); - - $return = []; - foreach ($set as $budget) { - Log::debug('First set, budget #' . $budget->id . ' (' . $budget->name . ')'); - $id = $budget->id; - if (!isset($return[$id])) { - Log::debug('$return[$id] is not set, now created.'); - $return[$id] = [ - 'budget' => $budget, - 'entries' => [], - ]; - } - Log::debug('Add new entry to entries, for ' . $budget->dateFormatted . ' and amount ' . $budget->sumAmount); - // store each entry: - $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; - } - unset($budget); - - // run the second set: - foreach ($secondSet as $entry) { - $id = $entry->id; - // create it if it still does not exist (not really likely) - if (!isset($return[$id])) { - $return[$id] = [ - 'budget' => $entry, - 'entries' => [], - ]; - } - // this one might be filled too: - $startAmount = $return[$id]['entries'][$entry->dateFormatted] ?? '0'; - // store each entry: - $return[$id]['entries'][$entry->dateFormatted] = bcadd($startAmount, $entry->sumAmount); - } - - return $return; - } - - /** - * Returns a list of budgets, budget limits and limit repetitions - * (doubling any of them in a left join) - * - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection - { - /** @var Collection $set */ - $set = $this->user - ->budgets() - ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') - ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->where( - function (Builder $query) use ($start, $end) { - $query->where( - function (Builder $query) use ($start, $end) { - $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')); - $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d')); - } - ); - $query->orWhere( - function (Builder $query) { - $query->whereNull('limit_repetitions.startdate'); - $query->whereNull('limit_repetitions.enddate'); - } - ); - } - ) - ->orderBy('budgets.id', 'budget_limits.startdate', 'limit_repetitions.enddate') - ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']); - - $set = $set->sortBy( - function (Budget $budget) { - return strtolower($budget->name); - } - ); - - return $set; - - } - - /** - * @param Budget $budget - * @param string $repeatFreq - * @param Carbon $start - * @param Carbon $end - * - * @return LimitRepetition - */ - public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition - { - $data = $budget->limitrepetitions() - ->where('budget_limits.repeat_freq', $repeatFreq) - ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) - ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00')) - ->first(['limit_repetitions.*']); - if (is_null($data)) { - return new LimitRepetition; - } - - return $data; - } - - /** - * Returns all expenses for the given budget and the given accounts, in the given period. - * - * @param Budget $budget - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection - { - $ids = $accounts->pluck('id')->toArray(); - $set = $budget->transactionjournals() - ->before($end) - ->after($start) - ->expanded() - ->where('transaction_types.type', TransactionType::WITHDRAWAL) - ->whereIn('source_account.id', $ids) - ->get(TransactionJournal::queryFields()); - - return $set; - } - - /** - * @param Budget $budget - * - * @return Carbon - */ - public function getFirstBudgetLimitDate(Budget $budget): Carbon - { - $limit = $budget->budgetlimits()->orderBy('startdate', 'ASC')->first(); - if ($limit) { - return $limit->startdate; - } - - return Carbon::now()->startOfYear(); - } +// /** +// * @param Budget $budget +// * +// * @return Carbon +// */ +// public function getFirstBudgetLimitDate(Budget $budget): Carbon +// { +// $limit = $budget->budgetlimits()->orderBy('startdate', 'ASC')->first(); +// if ($limit) { +// return $limit->startdate; +// } +// +// return Carbon::now()->startOfYear(); +// } /** * @return Collection @@ -530,315 +523,315 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $set; } - /** - * Returns all the transaction journals for a limit, possibly limited by a limit repetition. - * - * @param Budget $budget - * @param LimitRepetition $repetition - * @param int $take - * - * @return LengthAwarePaginator - */ - public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator - { - $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; - $setQuery = $budget->transactionjournals()->expanded() - ->take($take)->offset($offset) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC'); - $countQuery = $budget->transactionjournals(); +// /** +// * Returns all the transaction journals for a limit, possibly limited by a limit repetition. +// * +// * @param Budget $budget +// * @param LimitRepetition $repetition +// * @param int $take +// * +// * @return LengthAwarePaginator +// */ +// public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator +// { +// $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; +// $setQuery = $budget->transactionjournals()->expanded() +// ->take($take)->offset($offset) +// ->orderBy('transaction_journals.date', 'DESC') +// ->orderBy('transaction_journals.order', 'ASC') +// ->orderBy('transaction_journals.id', 'DESC'); +// $countQuery = $budget->transactionjournals(); +// +// +// if (!is_null($repetition->id)) { +// $setQuery->after($repetition->startdate)->before($repetition->enddate); +// $countQuery->after($repetition->startdate)->before($repetition->enddate); +// } +// +// +// $set = $setQuery->get(TransactionJournal::queryFields()); +// $count = $countQuery->count(); +// +// +// $paginator = new LengthAwarePaginator($set, $count, $take, $offset); +// +// return $paginator; +// } +// /** +// * Returns a list of budget limits that are valid in the current given range. +// * $ignore is optional. Send an empty limit rep. +// * +// * @param Budget $budget +// * @param Carbon $start +// * @param Carbon $end +// * @param LimitRepetition $ignore +// * +// * @return Collection +// */ +// public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection +// { +// $query = $budget->limitrepetitions() +// // starts before start time, and the end also after start time. +// ->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')) +// ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); +// if (!is_null($ignore->id)) { +// $query->where('limit_repetitions.id', '!=', $ignore->id); +// } +// $data = $query->get(['limit_repetitions.*']); +// +// return $data; +// } - if (!is_null($repetition->id)) { - $setQuery->after($repetition->startdate)->before($repetition->enddate); - $countQuery->after($repetition->startdate)->before($repetition->enddate); - } +// /** +// * @param Carbon $start +// * @param Carbon $end +// * @param int $page +// * @param int $pageSize +// * +// * @return LengthAwarePaginator +// */ +// public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator +// { +// $offset = ($page - 1) * $pageSize; +// $query = $this->user +// ->transactionjournals() +// ->expanded() +// ->where('transaction_types.type', TransactionType::WITHDRAWAL) +// ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') +// ->whereNull('budget_transaction_journal.id') +// ->before($end) +// ->after($start); +// +// $count = $query->count(); +// $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); +// $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); +// +// return $paginator; +// } +// /** +// * @param Collection $accounts +// * @param Carbon $start +// * @param Carbon $end +// * +// * @return Collection +// */ +// public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection +// { +// $ids = $accounts->pluck('id')->toArray(); +// +// return $this->user +// ->transactionjournals() +// ->expanded() +// ->whereIn('source_account.id', $ids) +// ->where('transaction_types.type', TransactionType::WITHDRAWAL) +// ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') +// ->whereNull('budget_transaction_journal.id') +// ->before($end) +// ->after($start) +// ->get(TransactionJournal::queryFields()); +// } - $set = $setQuery->get(TransactionJournal::queryFields()); - $count = $countQuery->count(); +// /** +// * @param Collection $accounts +// * @param Carbon $start +// * @param Carbon $end +// * +// * @return string +// */ +// public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string +// { +// $ids = $accounts->pluck('id')->toArray(); +// $entry = $this->user +// ->transactionjournals() +// ->whereNotIn( +// 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { +// $query +// ->select('transaction_journals.id') +// ->from('transaction_journals') +// ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') +// ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) +// ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) +// ->whereNotNull('budget_transaction_journal.budget_id'); +// } +// ) +// ->after($start) +// ->before($end) +// ->leftJoin( +// 'transactions', function (JoinClause $join) { +// $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); +// } +// ) +// ->whereIn('transactions.account_id', $ids) +// //->having('transaction_count', '=', 1) TODO check if this still works +// ->transactionTypes([TransactionType::WITHDRAWAL]) +// ->first( +// [ +// DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), +// DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), +// ] +// ); +// if (is_null($entry)) { +// return '0'; +// } +// if (is_null($entry->journalAmount)) { +// return '0'; +// } +// +// return $entry->journalAmount; +// } +// /** +// * Returns an array with the following key:value pairs: +// * +// * yyyy-mm-dd: +// * +// * That array contains: +// * +// * budgetid: +// * +// * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget +// * from the given users accounts.. +// * +// * @param Collection $accounts +// * @param Carbon $start +// * @param Carbon $end +// * +// * @return array +// */ +// public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array +// { +// $ids = $accounts->pluck('id')->toArray(); +// /** @var Collection $query */ +// $query = $this->user->transactionJournals() +// ->transactionTypes([TransactionType::WITHDRAWAL]) +// ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') +// ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') +// ->whereIn('transactions.account_id', $ids) +// ->where('transactions.amount', '<', 0) +// ->before($end) +// ->after($start) +// ->groupBy('budget_id') +// ->groupBy('dateFormatted') +// ->get( +// ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', +// DB::raw('SUM(`transactions`.`amount`) AS `sum`')] +// ); +// +// $return = []; +// foreach ($query->toArray() as $entry) { +// $budgetId = $entry['budget_id']; +// if (!isset($return[$budgetId])) { +// $return[$budgetId] = []; +// } +// $return[$budgetId][$entry['dateFormatted']] = $entry['sum']; +// } +// +// return $return; +// } - $paginator = new LengthAwarePaginator($set, $count, $take, $offset); +// /** +// * Returns a list of expenses (in the field "spent", grouped per budget per account. +// * +// * @param Collection $budgets +// * @param Collection $accounts +// * @param Carbon $start +// * @param Carbon $end +// * +// * @return Collection +// */ +// public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection +// { +// $accountIds = $accounts->pluck('id')->toArray(); +// $budgetIds = $budgets->pluck('id')->toArray(); +// $set = $this->user->transactionjournals() +// ->leftJoin( +// 'transactions AS t_from', function (JoinClause $join) { +// $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); +// } +// ) +// ->leftJoin( +// 'transactions AS t_to', function (JoinClause $join) { +// $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); +// } +// ) +// ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') +// ->whereIn('t_from.account_id', $accountIds) +// ->whereNotIn('t_to.account_id', $accountIds) +// ->where( +// function (Builder $q) use ($budgetIds) { +// $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); +// $q->orWhereNull('budget_transaction_journal.budget_id'); +// } +// ) +// ->after($start) +// ->before($end) +// ->groupBy('t_from.account_id') +// ->groupBy('budget_transaction_journal.budget_id') +// ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense. +// ->get( +// [ +// 't_from.account_id', 'budget_transaction_journal.budget_id', +// DB::raw('SUM(`t_from`.`amount`) AS `spent`'), +// ] +// ); +// +// return $set; +// +// } - return $paginator; - } - - /** - * Returns a list of budget limits that are valid in the current given range. - * $ignore is optional. Send an empty limit rep. - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param LimitRepetition $ignore - * - * @return Collection - */ - public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection - { - $query = $budget->limitrepetitions() - // starts before start time, and the end also after start time. - ->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')) - ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); - if (!is_null($ignore->id)) { - $query->where('limit_repetitions.id', '!=', $ignore->id); - } - $data = $query->get(['limit_repetitions.*']); - - return $data; - } - - /** - * @param Carbon $start - * @param Carbon $end - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator - { - $offset = ($page - 1) * $pageSize; - $query = $this->user - ->transactionjournals() - ->expanded() - ->where('transaction_types.type', TransactionType::WITHDRAWAL) - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('budget_transaction_journal.id') - ->before($end) - ->after($start); - - $count = $query->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); - $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); - - return $paginator; - } - - /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); - - return $this->user - ->transactionjournals() - ->expanded() - ->whereIn('source_account.id', $ids) - ->where('transaction_types.type', TransactionType::WITHDRAWAL) - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('budget_transaction_journal.id') - ->before($end) - ->after($start) - ->get(TransactionJournal::queryFields()); - } - - /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string - { - $ids = $accounts->pluck('id')->toArray(); - $entry = $this->user - ->transactionjournals() - ->whereNotIn( - 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { - $query - ->select('transaction_journals.id') - ->from('transaction_journals') - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) - ->whereNotNull('budget_transaction_journal.budget_id'); - } - ) - ->after($start) - ->before($end) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->whereIn('transactions.account_id', $ids) - //->having('transaction_count', '=', 1) TODO check if this still works - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->first( - [ - DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), - DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), - ] - ); - if (is_null($entry)) { - return '0'; - } - if (is_null($entry->journalAmount)) { - return '0'; - } - - return $entry->journalAmount; - } - - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * That array contains: - * - * budgetid: - * - * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget - * from the given users accounts.. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array - { - $ids = $accounts->pluck('id')->toArray(); - /** @var Collection $query */ - $query = $this->user->transactionJournals() - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->whereIn('transactions.account_id', $ids) - ->where('transactions.amount', '<', 0) - ->before($end) - ->after($start) - ->groupBy('budget_id') - ->groupBy('dateFormatted') - ->get( - ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', - DB::raw('SUM(`transactions`.`amount`) AS `sum`')] - ); - - $return = []; - foreach ($query->toArray() as $entry) { - $budgetId = $entry['budget_id']; - if (!isset($return[$budgetId])) { - $return[$budgetId] = []; - } - $return[$budgetId][$entry['dateFormatted']] = $entry['sum']; - } - - return $return; - } - - /** - * Returns a list of expenses (in the field "spent", grouped per budget per account. - * - * @param Collection $budgets - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection - { - $accountIds = $accounts->pluck('id')->toArray(); - $budgetIds = $budgets->pluck('id')->toArray(); - $set = $this->user->transactionjournals() - ->leftJoin( - 'transactions AS t_from', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_to', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); - } - ) - ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->whereIn('t_from.account_id', $accountIds) - ->whereNotIn('t_to.account_id', $accountIds) - ->where( - function (Builder $q) use ($budgetIds) { - $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); - $q->orWhereNull('budget_transaction_journal.budget_id'); - } - ) - ->after($start) - ->before($end) - ->groupBy('t_from.account_id') - ->groupBy('budget_transaction_journal.budget_id') - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense. - ->get( - [ - 't_from.account_id', 'budget_transaction_journal.budget_id', - DB::raw('SUM(`t_from`.`amount`) AS `spent`'), - ] - ); - - return $set; - - } - - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget - * from all the users accounts. - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array - { - /** @var Collection $query */ - $query = $budget->transactionjournals() - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.amount', '<', 0) - ->before($end) - ->after($start) - ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); - - $return = []; - foreach ($query->toArray() as $entry) { - $return[$entry['dateFormatted']] = $entry['sum']; - } - - // also search transactions: - $query = $budget->transactions() - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->where('transactions.amount', '<', 0) - ->before($end) - ->after($start) - ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); - foreach ($query as $newEntry) { - // add to return array. - $date = $newEntry['dateFormatted']; - if (isset($return[$date])) { - $return[$date] = bcadd($newEntry['sum'], $return[$date]); - continue; - } - - $return[$date] = $newEntry['sum']; - } - - return $return; - } +// /** +// * Returns an array with the following key:value pairs: +// * +// * yyyy-mm-dd: +// * +// * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget +// * from all the users accounts. +// * +// * @param Budget $budget +// * @param Carbon $start +// * @param Carbon $end +// * @param Collection $accounts +// * +// * @return array +// */ +// public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array +// { +// /** @var Collection $query */ +// $query = $budget->transactionjournals() +// ->transactionTypes([TransactionType::WITHDRAWAL]) +// ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') +// ->where('transactions.amount', '<', 0) +// ->before($end) +// ->after($start) +// ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); +// +// $return = []; +// foreach ($query->toArray() as $entry) { +// $return[$entry['dateFormatted']] = $entry['sum']; +// } +// +// // also search transactions: +// $query = $budget->transactions() +// ->transactionTypes([TransactionType::WITHDRAWAL]) +// ->where('transactions.amount', '<', 0) +// ->before($end) +// ->after($start) +// ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); +// foreach ($query as $newEntry) { +// // add to return array. +// $date = $newEntry['dateFormatted']; +// if (isset($return[$date])) { +// $return[$date] = bcadd($newEntry['sum'], $return[$date]); +// continue; +// } +// +// $return[$date] = $newEntry['sum']; +// } +// +// return $return; +// } /** * @param array $data diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 577b5e18aa..0ec995f41b 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -4,11 +4,8 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; -use FireflyIII\Models\Account; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\LimitRepetition; -use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; /** @@ -19,23 +16,23 @@ use Illuminate\Support\Collection; interface BudgetRepositoryInterface { - /** - * - * Same as ::spentInPeriod but corrects journals for a set of accounts - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return string - */ - public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts); + // /** + // * + // * Same as ::spentInPeriod but corrects journals for a set of accounts + // * + // * @param Budget $budget + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return string + // */ + // public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts); - /** - * @return bool - */ - public function cleanupBudgets(): bool; + // /** + // * @return bool + // */ + // public function cleanupBudgets(): bool; /** * @param Budget $budget @@ -44,15 +41,15 @@ interface BudgetRepositoryInterface */ public function destroy(Budget $budget): bool; - /** - * @param Budget $budget - * @param Account $account - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection; + // /** + // * @param Budget $budget + // * @param Account $account + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection; /** * Find a budget. @@ -63,12 +60,12 @@ interface BudgetRepositoryInterface */ public function find(int $budgetId): Budget; - /** - * @param Budget $budget - * - * @return Carbon - */ - public function firstActivity(Budget $budget): Carbon; + // /** + // * @param Budget $budget + // * + // * @return Carbon + // */ + // public function firstActivity(Budget $budget): Carbon; /** * @return Collection @@ -78,209 +75,208 @@ interface BudgetRepositoryInterface /** * @param Carbon $start * @param Carbon $end - * @param Budget $budget * * @return Collection */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end, Budget $budget = null): Collection; + public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection; - /** - * @param Account $account - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection; + // /** + // * @param Account $account + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection; - /** - * Get the budgeted amounts for each budgets in each year. - * - * @param Collection $budgets - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection; + // /** + // * Get the budgeted amounts for each budgets in each year. + // * + // * @param Collection $budgets + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection; /** * @return Collection */ public function getBudgets(): Collection; - /** - * Returns an array with every budget in it and the expenses for each budget - * per month. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array; + // /** + // * Returns an array with every budget in it and the expenses for each budget + // * per month. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return array + // */ + // public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array; - /** - * Returns an array with every budget in it and the expenses for each budget - * per year for. - * - * @param Collection $budgets - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @deprecated - * - * @return array - */ - public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array; + // /** + // * Returns an array with every budget in it and the expenses for each budget + // * per year for. + // * + // * @param Collection $budgets + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @deprecated + // * + // * @return array + // */ + // public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array; - /** - * Returns a list of budgets, budget limits and limit repetitions - * (doubling any of them in a left join) - * - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection; + // /** + // * Returns a list of budgets, budget limits and limit repetitions + // * (doubling any of them in a left join) + // * + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection; - /** - * Returns a list of budget limits that are valid in the current given range. - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param LimitRepetition $ignore - * - * @return Collection - */ - public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection; + // /** + // * Returns a list of budget limits that are valid in the current given range. + // * + // * @param Budget $budget + // * @param Carbon $start + // * @param Carbon $end + // * @param LimitRepetition $ignore + // * + // * @return Collection + // */ + // public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection; - /** - * @param Budget $budget - * @param string $repeatFreq - * @param Carbon $start - * @param Carbon $end - * - * @return LimitRepetition - */ - public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition; + // /** + // * @param Budget $budget + // * @param string $repeatFreq + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return LimitRepetition + // */ + // public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition; - /** - * Returns all expenses for the given budget and the given accounts, in the given period. - * - * @param Budget $budget - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection; + // /** + // * Returns all expenses for the given budget and the given accounts, in the given period. + // * + // * @param Budget $budget + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection; - /** - * @param Budget $budget - * - * @return Carbon - */ - public function getFirstBudgetLimitDate(Budget $budget):Carbon; + // /** + // * @param Budget $budget + // * + // * @return Carbon + // */ + // public function getFirstBudgetLimitDate(Budget $budget):Carbon; /** * @return Collection */ public function getInactiveBudgets(): Collection; - /** - * Returns all the transaction journals for a limit, possibly limited by a limit repetition. - * - * @param Budget $budget - * @param LimitRepetition $repetition - * @param int $take - * - * @return LengthAwarePaginator - */ - public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator; + // /** + // * Returns all the transaction journals for a limit, possibly limited by a limit repetition. + // * + // * @param Budget $budget + // * @param LimitRepetition $repetition + // * @param int $take + // * + // * @return LengthAwarePaginator + // */ + // public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator; - /** - * @param Carbon $start - * @param Carbon $end - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator; + // /** + // * @param Carbon $start + // * @param Carbon $end + // * @param int $page + // * @param int $pageSize + // * + // * @return LengthAwarePaginator + // */ + // public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator; - /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection; + // /** + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection; - /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string; + // /** + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return string + // */ + // public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string; - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * That array contains: - * - * budgetid: - * - * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget - * from the given users accounts.. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array; + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * That array contains: + // * + // * budgetid: + // * + // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget + // * from the given users accounts.. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return array + // */ + // public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array; - /** - * Returns a list of expenses (in the field "spent", grouped per budget per account. - * - * @param Collection $budgets - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection; + // /** + // * Returns a list of expenses (in the field "spent", grouped per budget per account. + // * + // * @param Collection $budgets + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection; - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget - * from all the users accounts. - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array; + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget + // * from all the users accounts. + // * + // * @param Budget $budget + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return array + // */ + // public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array; /** * @param array $data From 6d944ec98faa348d6df7973d8c4a67714bcc0395 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 6 May 2016 06:15:46 +0200 Subject: [PATCH 087/206] More cleanup for budgets. --- app/Helpers/Report/BalanceReportHelper.php | 6 +- app/Helpers/Report/BudgetReportHelper.php | 40 +- app/Http/Controllers/BudgetController.php | 87 +- .../Controllers/Popup/ReportController.php | 13 +- app/Models/LimitRepetition.php | 24 + app/Models/TransactionJournal.php | 4 + app/Repositories/Budget/BudgetRepository.php | 843 ++++++++++-------- .../Budget/BudgetRepositoryInterface.php | 61 +- .../Journal/JournalRepository.php | 4 +- 9 files changed, 641 insertions(+), 441 deletions(-) diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index 472ab2dc88..d36bc46025 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -68,8 +68,10 @@ class BalanceReportHelper implements BalanceReportHelperInterface // build a balance header: $header = new BalanceHeader; - $budgets = new Collection;// $this->budgetRepository->getBudgetsAndLimitsInRange($start, $end); // TODO BUDGET getBudgets - $spentData = new Collection; // $this->budgetRepository->spentPerBudgetPerAccount($budgets, $accounts, $start, $end); TODO BUDGET journalsInPeriod + // new Collection;// $this->budgetRepository->getBudgetsAndLimitsInRange($start, $end); // TO DO BUDGET getBudgets + $budgets = $this->budgetRepository->getBudgets(); + // new Collection; // $this->budgetRepository->spentPerBudgetPerAccount($budgets, $accounts, $start, $end); TO DO BUDGET journalsInPeriod + $spentData = $this->budgetRepository->journalsInPeriod($budgets, $accounts, $start, $end); foreach ($accounts as $account) { $header->addAccount($account); } diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 04124aace4..6f72f68777 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -16,8 +16,10 @@ use FireflyIII\Helpers\Collection\Budget as BudgetCollection; use FireflyIII\Helpers\Collection\BudgetLine; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Illuminate\Support\Collection; +use Log; /** * Class BudgetReportHelper @@ -38,24 +40,21 @@ class BudgetReportHelper implements BudgetReportHelperInterface { $object = new BudgetCollection; /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $set = $repository->getBudgets(); - $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end); - $allTotalSpent = '0'; //$repository->spentAllPerDayForAccounts($accounts, $start, $end);// TODO BUDGET MASSIVELY STUPID SPECIFIC METHOD + $repository = app(BudgetRepositoryInterface::class); + $set = $repository->getBudgets(); + /** @var Budget $budget */ foreach ($set as $budget) { - - $repetitions = $allRepetitions->filter( - function (LimitRepetition $rep) use ($budget) { - return $rep->budget_id == $budget->id; - } - ); - $totalSpent = $allTotalSpent[$budget->id] ?? []; + Log::debug('Now at budget #' . $budget->id . ' (' . $budget->name . ')'); + $repetitions = $budget->limitrepetitions()->before($end)->after($start)->get(); // no repetition(s) for this budget: if ($repetitions->count() == 0) { + Log::debug('Found zero repetitions.'); + // spent for budget in time range: + $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end); - $spent = array_sum($totalSpent); + // $spent = array_sum($totalSpent); if ($spent > 0) { $budgetLine = new BudgetLine; $budgetLine->setBudget($budget); @@ -65,18 +64,16 @@ class BudgetReportHelper implements BudgetReportHelperInterface } continue; } - + Log::debug('Found ' . $repetitions->count() . ' repetitions.'); // one or more repetitions for budget: /** @var LimitRepetition $repetition */ foreach ($repetitions as $repetition) { + $budgetLine = new BudgetLine; $budgetLine->setBudget($budget); $budgetLine->setRepetition($repetition); - $expenses = $this->getSumOfRange($repetition->startdate, $repetition->enddate, $totalSpent); - - // 200 en -100 is 100, vergeleken met 0 === 1 - // 200 en -200 is 0, vergeleken met 0 === 0 - // 200 en -300 is -100, vergeleken met 0 === -1 + $expenses = $repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate); + Log::debug('Spent in p. [' . $repetition->startdate->format('Y-m-d') . '] to [' . $repetition->enddate->format('Y-m-d') . '] is ' . $expenses); $left = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? bcadd($repetition->amount, $expenses) : '0'; $spent = bccomp(bcadd($repetition->amount, $expenses), '0') === 1 ? $expenses : '0'; @@ -98,7 +95,12 @@ class BudgetReportHelper implements BudgetReportHelperInterface } // stuff outside of budgets: - $noBudget = '0'; //$repository->getWithoutBudgetSum($accounts, $start, $end); // TODO BUDGET journalsInPeriodWithoutBudget + $outsideBudget = $repository->journalsInPeriodWithoutBudget($accounts, $start, $end); + $noBudget = '0'; + /** @var TransactionJournal $journal */ + foreach ($outsideBudget as $journal) { + $noBudget = bcadd($noBudget, TransactionJournal::amount($journal)); + } $budgetLine = new BudgetLine; $budgetLine->setOverspent($noBudget); $budgetLine->setSpent($noBudget); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 0436135375..6188775438 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -13,6 +13,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Input; +use Log; use Navigation; use Preferences; use Response; @@ -174,6 +175,8 @@ class BudgetController extends Controller $periodStart = $start->formatLocalized($this->monthAndDayFormat); $periodEnd = $end->formatLocalized($this->monthAndDayFormat); $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + $startAsString = $start->format('Y-m-d'); + $endAsString = $end->format('Y-m-d'); /** * Do some cleanup: @@ -183,12 +186,28 @@ class BudgetController extends Controller // loop the budgets: /** @var Budget $budget */ foreach ($budgets as $budget) { - $budget->spent = '0';//$repository->balanceInPeriod($budget, $start, $end, $accounts); // TODO BUDGET spentInPeriod - $budget->currentRep = new LimitRepetition( - ); // $repository->getCurrentRepetition($budget, $repeatFreq, $start, $end); // TODO BUDGET getBudgetLimitRepetitions - $budget->otherRepetitions = new Collection( - );//$repository->getValidRepetitions($budget, $start, $end, $budget->currentRep); // TODO BUDGET getBudgetLimitRepetitions - if (!is_null($budget->currentRep->id)) { + $budget->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end); + $allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end); + $otherRepetitions = new Collection; + + /** @var LimitRepetition $repetition */ + foreach ($allRepetitions as $repetition) { + if ($repetition->budget_id == $budget->id) { + Log::debug('Repetition #' . $repetition->id . ' with budget #' . $repetition->budget_id . ' (current = ' . $budget->id . ')'); + if ($repetition->budgetLimit->repeat_freq == $repeatFreq + && $repetition->startdate->format('Y-m-d') == $startAsString + && $repetition->enddate->format('Y-m-d') == $endAsString + ) { + // do something + $budget->currentRep = $repetition; + continue; + } + $otherRepetitions->push($repetition); + } + } + $budget->otherRepetitions = $otherRepetitions; + + if (!is_null($budget->currentRep) && !is_null($budget->currentRep->id)) { $budgeted = bcadd($budgeted, $budget->currentRep->amount); } $spent = bcadd($spent, $budget->spent); @@ -223,7 +242,11 @@ class BudgetController extends Controller $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); $pageSize = Preferences::get('transactionPageSize', 50)->data; - $list = new LengthAwarePaginator([], 0, $pageSize); // $repository->getWithoutBudget($start, $end, $page, $pageSize); // TODO BUDGET journalsInPeriodWithoutBudget + $offset = ($page - 1) * $pageSize; + $journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); + $count = $journals->count(); + $journals = $journals->slice($offset, $pageSize); + $list = new LengthAwarePaginator($journals, $count, $pageSize); $subTitle = trans( 'firefly.without_budget_between', ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] @@ -260,25 +283,30 @@ class BudgetController extends Controller */ public function show(BudgetRepositoryInterface $repository, Budget $budget) { - $pageSize = Preferences::get('transactionPageSize', 50)->data; - $journals = new LengthAwarePaginator( - [], 0, $pageSize - ); //$repository->getJournals($budget, new LimitRepetition, $pageSize); // TODO BUDGET journalsInPeriod - $start = new Carbon; //$repository->firstActivity($budget); // TODO BUDGET getOldestJournal + /** @var Carbon $start */ + $start = session('first', Carbon::create()->startOfYear()); $end = new Carbon; + $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $offset = ($page - 1) * $pageSize; + $journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); + $count = $journals->count(); + $journals = $journals->slice($offset, $pageSize); + $journals = new LengthAwarePaginator($journals, $count, $pageSize); + + $journals->setPath('/budgets/show/' . $budget->id); + + $set = $budget->limitrepetitions()->orderBy('startdate', 'DESC')->get(); $subTitle = e($budget->name); - $journals->setPath('/budgets/show/' . $budget->id); - $spentArray = []; //$repository->spentPerDay($budget, $start, $end, new Collection); // TODO BUDGET spentInPeriod - $limits = new Collection(); + $limits = new Collection(); /** @var LimitRepetition $entry */ foreach ($set as $entry) { - $entry->spent = $this->getSumOfRange($entry->startdate, $entry->enddate, $spentArray); + $entry->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $entry->startdate, $entry->enddate); $limits->push($entry); } - return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle')); } @@ -295,23 +323,24 @@ class BudgetController extends Controller if ($repetition->budgetLimit->budget->id != $budget->id) { throw new FireflyException('This budget limit is not part of this budget.'); } - - $pageSize = Preferences::get('transactionPageSize', 50)->data; - $journals = new LengthAwarePaginator([], 0, $pageSize); // $repository->getJournals($budget, $repetition, $pageSize); // TODO BUDGET journalsInPeriod $start = $repetition->startdate; $end = $repetition->enddate; - $set = new Collection([$repetition]); - $subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]); + $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $offset = ($page - 1) * $pageSize; + $journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); + $count = $journals->count(); + $journals = $journals->slice($offset, $pageSize); + $journals = new LengthAwarePaginator($journals, $count, $pageSize); + $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id); - $spentArray = []; //$repository->spentPerDay($budget, $start, $end, new Collection); // TODO BUDGET spentInPeriod - $limits = new Collection(); - /** @var LimitRepetition $entry */ - foreach ($set as $entry) { - $entry->spent = $this->getSumOfRange($entry->startdate, $entry->enddate, $spentArray); - $limits->push($entry); - } + $subTitle = trans( + 'firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)] + ); + $repetition->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate); + $limits = new Collection([$repetition]); return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle')); diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index 048043c060..cbf4bc3010 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -14,7 +14,6 @@ namespace FireflyIII\Http\Controllers\Popup; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collection\BalanceLine; -use FireflyIII\Helpers\Csv\Mapper\Budget; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -94,15 +93,17 @@ class ReportController extends Controller switch (true) { case ($role === BalanceLine::ROLE_DEFAULTROLE && !is_null($budget->id)): - $journals = new Collection;// $budgetRepository->expensesSplit($budget, $account, $attributes['startDate'], $attributes['endDate']);// TODO BUDGET journalsInPeriod + $journals = $budgetRepository->journalsInPeriod( + new Collection([$budget]), new Collection([$account]), $attributes['startDate'], $attributes['endDate'] + ); break; case ($role === BalanceLine::ROLE_DEFAULTROLE && is_null($budget->id)): $budget->name = strval(trans('firefly.no_budget')); - $journals = new Collection;// $budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriodWithoutBudget + $journals = $budgetRepository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); break; case ($role === BalanceLine::ROLE_DIFFROLE): // journals no budget, not corrected by a tag. - $journals = new Collection; //$budgetRepository->getAllWithoutBudget($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriodWithoutBudget + $journals = $budgetRepository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); $budget->name = strval(trans('firefly.leftUnbalanced')); $journals = $journals->filter( function (TransactionJournal $journal) { @@ -138,10 +139,10 @@ class ReportController extends Controller $repository = app(BudgetRepositoryInterface::class); $budget = $repository->find(intval($attributes['budgetId'])); if (is_null($budget->id)) { - $journals = new Collection;// $repository->getWithoutBudgetForAccounts($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriodWithoutBudget + $journals = $repository->journalsInPeriodWithoutBudget($attributes['accounts'], $attributes['startDate'], $attributes['endDate']); } else { // get all expenses in budget in period: - $journals = new Collection; //$repository->getExpenses($budget, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); // TODO BUDGET journalsInPeriod + $journals = $repository->journalsInPeriod(new Collection([$budget]), $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); } $view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render(); diff --git a/app/Models/LimitRepetition.php b/app/Models/LimitRepetition.php index 7257b67689..590e32fca5 100644 --- a/app/Models/LimitRepetition.php +++ b/app/Models/LimitRepetition.php @@ -1,6 +1,8 @@ belongsTo('FireflyIII\Models\BudgetLimit'); } + /** + * + * @param Builder $query + * @param Carbon $date + * + */ + public function scopeAfter(Builder $query, Carbon $date) + { + $query->where('limit_repetitions.startdate', '>=', $date->format('Y-m-d 00:00:00')); + } + + /** + * + * @param Builder $query + * @param Carbon $date + * + */ + public function scopeBefore(Builder $query, Carbon $date) + { + $query->where('limit_repetitions.enddate', '<=', $date->format('Y-m-d 00:00:00')); + } + /** * @param $value */ diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 6a16338f95..2e9fe0011f 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -338,6 +338,10 @@ class TransactionJournal extends TransactionJournalSupport $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); } ); + + // order + $query->orderBy('transaction_journals.date', 'DESC')->orderBy('transaction_journals.order', 'ASC')->orderBy('transaction_journals.id', 'DESC'); + $query->groupBy('transaction_journals.id'); $query->with(['categories', 'budgets', 'attachments', 'bill','transactions']); } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index f55774f5c8..ecc53cdeb8 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -4,7 +4,6 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; -use DB; use FireflyIII\Events\BudgetLimitStored; use FireflyIII\Events\BudgetLimitUpdated; use FireflyIII\Models\Budget; @@ -12,21 +11,18 @@ use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Shared\ComponentRepository; use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Query\Builder as QueryBuilder; -use Illuminate\Database\Query\JoinClause; -use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; -use Input; +use Log; /** * Class BudgetRepository * * @package FireflyIII\Repositories\Budget */ -class BudgetRepository extends ComponentRepository implements BudgetRepositoryInterface +class BudgetRepository implements BudgetRepositoryInterface { /** @var User */ private $user; @@ -445,66 +441,66 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn // // } -// /** -// * @param Budget $budget -// * @param string $repeatFreq -// * @param Carbon $start -// * @param Carbon $end -// * -// * @return LimitRepetition -// */ -// public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition -// { -// $data = $budget->limitrepetitions() -// ->where('budget_limits.repeat_freq', $repeatFreq) -// ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) -// ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00')) -// ->first(['limit_repetitions.*']); -// if (is_null($data)) { -// return new LimitRepetition; -// } -// -// return $data; -// } + // /** + // * @param Budget $budget + // * @param string $repeatFreq + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return LimitRepetition + // */ + // public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition + // { + // $data = $budget->limitrepetitions() + // ->where('budget_limits.repeat_freq', $repeatFreq) + // ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) + // ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00')) + // ->first(['limit_repetitions.*']); + // if (is_null($data)) { + // return new LimitRepetition; + // } + // + // return $data; + // } -// /** -// * Returns all expenses for the given budget and the given accounts, in the given period. -// * -// * @param Budget $budget -// * @param Collection $accounts -// * @param Carbon $start -// * @param Carbon $end -// * -// * @return Collection -// */ -// public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection -// { -// $ids = $accounts->pluck('id')->toArray(); -// $set = $budget->transactionjournals() -// ->before($end) -// ->after($start) -// ->expanded() -// ->where('transaction_types.type', TransactionType::WITHDRAWAL) -// ->whereIn('source_account.id', $ids) -// ->get(TransactionJournal::queryFields()); -// -// return $set; -// } + // /** + // * Returns all expenses for the given budget and the given accounts, in the given period. + // * + // * @param Budget $budget + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection + // { + // $ids = $accounts->pluck('id')->toArray(); + // $set = $budget->transactionjournals() + // ->before($end) + // ->after($start) + // ->expanded() + // ->where('transaction_types.type', TransactionType::WITHDRAWAL) + // ->whereIn('source_account.id', $ids) + // ->get(TransactionJournal::queryFields()); + // + // return $set; + // } -// /** -// * @param Budget $budget -// * -// * @return Carbon -// */ -// public function getFirstBudgetLimitDate(Budget $budget): Carbon -// { -// $limit = $budget->budgetlimits()->orderBy('startdate', 'ASC')->first(); -// if ($limit) { -// return $limit->startdate; -// } -// -// return Carbon::now()->startOfYear(); -// } + // /** + // * @param Budget $budget + // * + // * @return Carbon + // */ + // public function getFirstBudgetLimitDate(Budget $budget): Carbon + // { + // $limit = $budget->budgetlimits()->orderBy('startdate', 'ASC')->first(); + // if ($limit) { + // return $limit->startdate; + // } + // + // return Carbon::now()->startOfYear(); + // } /** * @return Collection @@ -523,315 +519,428 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $set; } -// /** -// * Returns all the transaction journals for a limit, possibly limited by a limit repetition. -// * -// * @param Budget $budget -// * @param LimitRepetition $repetition -// * @param int $take -// * -// * @return LengthAwarePaginator -// */ -// public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator -// { -// $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; -// $setQuery = $budget->transactionjournals()->expanded() -// ->take($take)->offset($offset) -// ->orderBy('transaction_journals.date', 'DESC') -// ->orderBy('transaction_journals.order', 'ASC') -// ->orderBy('transaction_journals.id', 'DESC'); -// $countQuery = $budget->transactionjournals(); -// -// -// if (!is_null($repetition->id)) { -// $setQuery->after($repetition->startdate)->before($repetition->enddate); -// $countQuery->after($repetition->startdate)->before($repetition->enddate); -// } -// -// -// $set = $setQuery->get(TransactionJournal::queryFields()); -// $count = $countQuery->count(); -// -// -// $paginator = new LengthAwarePaginator($set, $count, $take, $offset); -// -// return $paginator; -// } + // /** + // * Returns all the transaction journals for a limit, possibly limited by a limit repetition. + // * + // * @param Budget $budget + // * @param LimitRepetition $repetition + // * @param int $take + // * + // * @return LengthAwarePaginator + // */ + // public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator + // { + // $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; + // $setQuery = $budget->transactionjournals()->expanded() + // ->take($take)->offset($offset) + // ->orderBy('transaction_journals.date', 'DESC') + // ->orderBy('transaction_journals.order', 'ASC') + // ->orderBy('transaction_journals.id', 'DESC'); + // $countQuery = $budget->transactionjournals(); + // + // + // if (!is_null($repetition->id)) { + // $setQuery->after($repetition->startdate)->before($repetition->enddate); + // $countQuery->after($repetition->startdate)->before($repetition->enddate); + // } + // + // + // $set = $setQuery->get(TransactionJournal::queryFields()); + // $count = $countQuery->count(); + // + // + // $paginator = new LengthAwarePaginator($set, $count, $take, $offset); + // + // return $paginator; + // } -// /** -// * Returns a list of budget limits that are valid in the current given range. -// * $ignore is optional. Send an empty limit rep. -// * -// * @param Budget $budget -// * @param Carbon $start -// * @param Carbon $end -// * @param LimitRepetition $ignore -// * -// * @return Collection -// */ -// public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection -// { -// $query = $budget->limitrepetitions() -// // starts before start time, and the end also after start time. -// ->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')) -// ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); -// if (!is_null($ignore->id)) { -// $query->where('limit_repetitions.id', '!=', $ignore->id); -// } -// $data = $query->get(['limit_repetitions.*']); -// -// return $data; -// } + // /** + // * Returns a list of budget limits that are valid in the current given range. + // * $ignore is optional. Send an empty limit rep. + // * + // * @param Budget $budget + // * @param Carbon $start + // * @param Carbon $end + // * @param LimitRepetition $ignore + // * + // * @return Collection + // */ + // public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection + // { + // $query = $budget->limitrepetitions() + // // starts before start time, and the end also after start time. + // ->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')) + // ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); + // if (!is_null($ignore->id)) { + // $query->where('limit_repetitions.id', '!=', $ignore->id); + // } + // $data = $query->get(['limit_repetitions.*']); + // + // return $data; + // } -// /** -// * @param Carbon $start -// * @param Carbon $end -// * @param int $page -// * @param int $pageSize -// * -// * @return LengthAwarePaginator -// */ -// public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator -// { -// $offset = ($page - 1) * $pageSize; -// $query = $this->user -// ->transactionjournals() -// ->expanded() -// ->where('transaction_types.type', TransactionType::WITHDRAWAL) -// ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') -// ->whereNull('budget_transaction_journal.id') -// ->before($end) -// ->after($start); -// -// $count = $query->count(); -// $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); -// $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); -// -// return $paginator; -// } + // /** + // * @param Carbon $start + // * @param Carbon $end + // * @param int $page + // * @param int $pageSize + // * + // * @return LengthAwarePaginator + // */ + // public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator + // { + // $offset = ($page - 1) * $pageSize; + // $query = $this->user + // ->transactionjournals() + // ->expanded() + // ->where('transaction_types.type', TransactionType::WITHDRAWAL) + // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->whereNull('budget_transaction_journal.id') + // ->before($end) + // ->after($start); + // + // $count = $query->count(); + // $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); + // $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); + // + // return $paginator; + // } -// /** -// * @param Collection $accounts -// * @param Carbon $start -// * @param Carbon $end -// * -// * @return Collection -// */ -// public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection -// { -// $ids = $accounts->pluck('id')->toArray(); -// -// return $this->user -// ->transactionjournals() -// ->expanded() -// ->whereIn('source_account.id', $ids) -// ->where('transaction_types.type', TransactionType::WITHDRAWAL) -// ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') -// ->whereNull('budget_transaction_journal.id') -// ->before($end) -// ->after($start) -// ->get(TransactionJournal::queryFields()); -// } + // /** + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // $ids = $accounts->pluck('id')->toArray(); + // + // return $this->user + // ->transactionjournals() + // ->expanded() + // ->whereIn('source_account.id', $ids) + // ->where('transaction_types.type', TransactionType::WITHDRAWAL) + // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->whereNull('budget_transaction_journal.id') + // ->before($end) + // ->after($start) + // ->get(TransactionJournal::queryFields()); + // } -// /** -// * @param Collection $accounts -// * @param Carbon $start -// * @param Carbon $end -// * -// * @return string -// */ -// public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string -// { -// $ids = $accounts->pluck('id')->toArray(); -// $entry = $this->user -// ->transactionjournals() -// ->whereNotIn( -// 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { -// $query -// ->select('transaction_journals.id') -// ->from('transaction_journals') -// ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') -// ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) -// ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) -// ->whereNotNull('budget_transaction_journal.budget_id'); -// } -// ) -// ->after($start) -// ->before($end) -// ->leftJoin( -// 'transactions', function (JoinClause $join) { -// $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); -// } -// ) -// ->whereIn('transactions.account_id', $ids) -// //->having('transaction_count', '=', 1) TODO check if this still works -// ->transactionTypes([TransactionType::WITHDRAWAL]) -// ->first( -// [ -// DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), -// DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), -// ] -// ); -// if (is_null($entry)) { -// return '0'; -// } -// if (is_null($entry->journalAmount)) { -// return '0'; -// } -// -// return $entry->journalAmount; -// } + // /** + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return string + // */ + // public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string + // { + // $ids = $accounts->pluck('id')->toArray(); + // $entry = $this->user + // ->transactionjournals() + // ->whereNotIn( + // 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { + // $query + // ->select('transaction_journals.id') + // ->from('transaction_journals') + // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) + // ->whereNotNull('budget_transaction_journal.budget_id'); + // } + // ) + // ->after($start) + // ->before($end) + // ->leftJoin( + // 'transactions', function (JoinClause $join) { + // $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + // } + // ) + // ->whereIn('transactions.account_id', $ids) + // //->having('transaction_count', '=', 1) TODO check if this still works + // ->transactionTypes([TransactionType::WITHDRAWAL]) + // ->first( + // [ + // DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), + // DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), + // ] + // ); + // if (is_null($entry)) { + // return '0'; + // } + // if (is_null($entry->journalAmount)) { + // return '0'; + // } + // + // return $entry->journalAmount; + // } -// /** -// * Returns an array with the following key:value pairs: -// * -// * yyyy-mm-dd: -// * -// * That array contains: -// * -// * budgetid: -// * -// * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget -// * from the given users accounts.. -// * -// * @param Collection $accounts -// * @param Carbon $start -// * @param Carbon $end -// * -// * @return array -// */ -// public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array -// { -// $ids = $accounts->pluck('id')->toArray(); -// /** @var Collection $query */ -// $query = $this->user->transactionJournals() -// ->transactionTypes([TransactionType::WITHDRAWAL]) -// ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') -// ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') -// ->whereIn('transactions.account_id', $ids) -// ->where('transactions.amount', '<', 0) -// ->before($end) -// ->after($start) -// ->groupBy('budget_id') -// ->groupBy('dateFormatted') -// ->get( -// ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', -// DB::raw('SUM(`transactions`.`amount`) AS `sum`')] -// ); -// -// $return = []; -// foreach ($query->toArray() as $entry) { -// $budgetId = $entry['budget_id']; -// if (!isset($return[$budgetId])) { -// $return[$budgetId] = []; -// } -// $return[$budgetId][$entry['dateFormatted']] = $entry['sum']; -// } -// -// return $return; -// } + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * That array contains: + // * + // * budgetid: + // * + // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget + // * from the given users accounts.. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return array + // */ + // public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array + // { + // $ids = $accounts->pluck('id')->toArray(); + // /** @var Collection $query */ + // $query = $this->user->transactionJournals() + // ->transactionTypes([TransactionType::WITHDRAWAL]) + // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + // ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + // ->whereIn('transactions.account_id', $ids) + // ->where('transactions.amount', '<', 0) + // ->before($end) + // ->after($start) + // ->groupBy('budget_id') + // ->groupBy('dateFormatted') + // ->get( + // ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', + // DB::raw('SUM(`transactions`.`amount`) AS `sum`')] + // ); + // + // $return = []; + // foreach ($query->toArray() as $entry) { + // $budgetId = $entry['budget_id']; + // if (!isset($return[$budgetId])) { + // $return[$budgetId] = []; + // } + // $return[$budgetId][$entry['dateFormatted']] = $entry['sum']; + // } + // + // return $return; + // } -// /** -// * Returns a list of expenses (in the field "spent", grouped per budget per account. -// * -// * @param Collection $budgets -// * @param Collection $accounts -// * @param Carbon $start -// * @param Carbon $end -// * -// * @return Collection -// */ -// public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection -// { -// $accountIds = $accounts->pluck('id')->toArray(); -// $budgetIds = $budgets->pluck('id')->toArray(); -// $set = $this->user->transactionjournals() -// ->leftJoin( -// 'transactions AS t_from', function (JoinClause $join) { -// $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); -// } -// ) -// ->leftJoin( -// 'transactions AS t_to', function (JoinClause $join) { -// $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); -// } -// ) -// ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') -// ->whereIn('t_from.account_id', $accountIds) -// ->whereNotIn('t_to.account_id', $accountIds) -// ->where( -// function (Builder $q) use ($budgetIds) { -// $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); -// $q->orWhereNull('budget_transaction_journal.budget_id'); -// } -// ) -// ->after($start) -// ->before($end) -// ->groupBy('t_from.account_id') -// ->groupBy('budget_transaction_journal.budget_id') -// ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense. -// ->get( -// [ -// 't_from.account_id', 'budget_transaction_journal.budget_id', -// DB::raw('SUM(`t_from`.`amount`) AS `spent`'), -// ] -// ); -// -// return $set; -// -// } + // /** + // * Returns a list of expenses (in the field "spent", grouped per budget per account. + // * + // * @param Collection $budgets + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // $accountIds = $accounts->pluck('id')->toArray(); + // $budgetIds = $budgets->pluck('id')->toArray(); + // $set = $this->user->transactionjournals() + // ->leftJoin( + // 'transactions AS t_from', function (JoinClause $join) { + // $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); + // } + // ) + // ->leftJoin( + // 'transactions AS t_to', function (JoinClause $join) { + // $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); + // } + // ) + // ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + // ->whereIn('t_from.account_id', $accountIds) + // ->whereNotIn('t_to.account_id', $accountIds) + // ->where( + // function (Builder $q) use ($budgetIds) { + // $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); + // $q->orWhereNull('budget_transaction_journal.budget_id'); + // } + // ) + // ->after($start) + // ->before($end) + // ->groupBy('t_from.account_id') + // ->groupBy('budget_transaction_journal.budget_id') + // ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense. + // ->get( + // [ + // 't_from.account_id', 'budget_transaction_journal.budget_id', + // DB::raw('SUM(`t_from`.`amount`) AS `spent`'), + // ] + // ); + // + // return $set; + // + // } -// /** -// * Returns an array with the following key:value pairs: -// * -// * yyyy-mm-dd: -// * -// * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget -// * from all the users accounts. -// * -// * @param Budget $budget -// * @param Carbon $start -// * @param Carbon $end -// * @param Collection $accounts -// * -// * @return array -// */ -// public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array -// { -// /** @var Collection $query */ -// $query = $budget->transactionjournals() -// ->transactionTypes([TransactionType::WITHDRAWAL]) -// ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') -// ->where('transactions.amount', '<', 0) -// ->before($end) -// ->after($start) -// ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); -// -// $return = []; -// foreach ($query->toArray() as $entry) { -// $return[$entry['dateFormatted']] = $entry['sum']; -// } -// -// // also search transactions: -// $query = $budget->transactions() -// ->transactionTypes([TransactionType::WITHDRAWAL]) -// ->where('transactions.amount', '<', 0) -// ->before($end) -// ->after($start) -// ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); -// foreach ($query as $newEntry) { -// // add to return array. -// $date = $newEntry['dateFormatted']; -// if (isset($return[$date])) { -// $return[$date] = bcadd($newEntry['sum'], $return[$date]); -// continue; -// } -// -// $return[$date] = $newEntry['sum']; -// } -// -// return $return; -// } + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget + // * from all the users accounts. + // * + // * @param Budget $budget + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return array + // */ + // public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array + // { + // /** @var Collection $query */ + // $query = $budget->transactionjournals() + // ->transactionTypes([TransactionType::WITHDRAWAL]) + // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + // ->where('transactions.amount', '<', 0) + // ->before($end) + // ->after($start) + // ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); + // + // $return = []; + // foreach ($query->toArray() as $entry) { + // $return[$entry['dateFormatted']] = $entry['sum']; + // } + // + // // also search transactions: + // $query = $budget->transactions() + // ->transactionTypes([TransactionType::WITHDRAWAL]) + // ->where('transactions.amount', '<', 0) + // ->before($end) + // ->after($start) + // ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); + // foreach ($query as $newEntry) { + // // add to return array. + // $date = $newEntry['dateFormatted']; + // if (isset($return[$date])) { + // $return[$date] = bcadd($newEntry['sum'], $return[$date]); + // continue; + // } + // + // $return[$date] = $newEntry['sum']; + // } + // + // return $return; + // } + + /** + * @param Collection $budgets + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection + { + $return = new Collection; + $accountIds = []; + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + } + + // first get all journals for all budget(s): + $journalQuery = $this->user->transactionjournals() + ->expanded() + ->before($end) + ->after($start) + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereIn('budget_transaction_journal.budget_id', $budgets->pluck('id')->toArray()); + // add account id's, if relevant: + if (count($accountIds) > 0) { + $journalQuery->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id'); + $journalQuery->whereIn('source.account_id', $accountIds); + } + // get them: + $journals = $journalQuery->get(TransactionJournal::queryFields()); + Log::debug('journalsInPeriod journal count is ' . $journals->count()); + + // then get transactions themselves. + $transactionQuery = $this->user->transactionjournals() + ->expanded() + ->before($end) + ->after($start) + ->leftJoin('transactions as related', 'related.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'related.id') + ->whereIn('budget_transaction.budget_id', $budgets->pluck('id')->toArray()); + + if (count($accountIds) > 0) { + $transactionQuery->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id'); + $transactionQuery->whereIn('source.account_id', $accountIds); + } + $transactions = $transactionQuery->get(TransactionJournal::queryFields()); + + // return complete set: + $return = $return->merge($transactions); + $return = $return->merge($journals); + + return $return; + } + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): Collection + { + /** @var Collection $set */ + $set = $this->user + ->transactionjournals() + ->expanded() + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('budget_transaction_journal.id') + ->before($end) + ->after($start)->with( + [ + 'transactions' => function (HasMany $query) { + $query->where('transactions.amount', '<', 0); + }, + 'transactions.budgets', + ] + )->get(TransactionJournal::queryFields()); + + $set = $set->filter( + function (TransactionJournal $journal) { + foreach ($journal->transactions as $t) { + if ($t->budgets->count() === 0) { + return $journal; + } + } + } + ); + return $set; + } + + /** + * @param Collection $budgets + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string + { + $set = $this->journalsInPeriod($budgets, $accounts, $start, $end); + Log::debug('spentInPeriod set count is ' . $set->count()); + $sum = '0'; + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $sum = bcadd($sum, TransactionJournal::amount($journal)); + } + + return $sum; + } /** * @param array $data diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 0ec995f41b..78d35421af 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -16,6 +16,13 @@ use Illuminate\Support\Collection; interface BudgetRepositoryInterface { + /** + * @param Budget $budget + * + * @return bool + */ + public function destroy(Budget $budget): bool; + // /** // * // * Same as ::spentInPeriod but corrects journals for a set of accounts @@ -35,11 +42,13 @@ interface BudgetRepositoryInterface // public function cleanupBudgets(): bool; /** - * @param Budget $budget + * Find a budget. * - * @return bool + * @param int $budgetId + * + * @return Budget */ - public function destroy(Budget $budget): bool; + public function find(int $budgetId): Budget; // /** // * @param Budget $budget @@ -52,13 +61,9 @@ interface BudgetRepositoryInterface // public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection; /** - * Find a budget. - * - * @param int $budgetId - * - * @return Budget + * @return Collection */ - public function find(int $budgetId): Budget; + public function getActiveBudgets(): Collection; // /** // * @param Budget $budget @@ -67,11 +72,6 @@ interface BudgetRepositoryInterface // */ // public function firstActivity(Budget $budget): Carbon; - /** - * @return Collection - */ - public function getActiveBudgets(): Collection; - /** * @param Carbon $start * @param Carbon $end @@ -80,6 +80,11 @@ interface BudgetRepositoryInterface */ public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection; + /** + * @return Collection + */ + public function getBudgets(): Collection; + // /** // * @param Account $account // * @param Collection $accounts @@ -104,7 +109,7 @@ interface BudgetRepositoryInterface /** * @return Collection */ - public function getBudgets(): Collection; + public function getInactiveBudgets(): Collection; // /** // * Returns an array with every budget in it and the expenses for each budget @@ -186,9 +191,33 @@ interface BudgetRepositoryInterface // public function getFirstBudgetLimitDate(Budget $budget):Carbon; /** + * @param Collection $budgets + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * * @return Collection */ - public function getInactiveBudgets(): Collection; + public function journalsInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection; + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): Collection; + + /** + * @param Collection $budgets + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string; // /** // * Returns all the transaction journals for a limit, possibly limited by a limit repetition. diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index d9ddb99c54..af03b870b3 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -132,8 +132,8 @@ class JournalRepository implements JournalRepositoryInterface if (count($types) > 0) { $query->transactionTypes($types); } - - $count = $query->count(); + $count = $this->user->transactionJournals()->transactionTypes($types)->count(); + Log::debug('getJournals() count: ' . $count); $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); From 27f5fe18dfc66e5b8713f1f0ebead77c5b451279 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 6 May 2016 10:32:26 +0200 Subject: [PATCH 088/206] Moving stuff around, optimising charts. --- app/Http/Controllers/AccountController.php | 1 + app/Http/Controllers/BudgetController.php | 8 +- .../Controllers/Chart/AccountController.php | 4 +- .../Controllers/Chart/BudgetController.php | 208 ++++++++++++------ app/Models/Transaction.php | 2 +- .../Account/AccountRepository.php | 2 +- app/Repositories/Budget/BudgetRepository.php | 96 +++++--- .../Budget/BudgetRepositoryInterface.php | 9 + .../Models/TransactionJournalSupport.php | 56 +++++ app/Support/Navigation.php | 3 + app/Support/Twig/Journal.php | 68 +++++- resources/views/list/journals.twig | 12 +- 12 files changed, 347 insertions(+), 122 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index e11f45afea..109d81aff2 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -6,6 +6,7 @@ use ExpandedForm; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; +use Illuminate\Support\Collection; use Input; use Preferences; use Session; diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 6188775438..8e3e1f6f87 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -289,7 +289,7 @@ class BudgetController extends Controller $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); $pageSize = Preferences::get('transactionPageSize', 50)->data; $offset = ($page - 1) * $pageSize; - $journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); + $journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); $count = $journals->count(); $journals = $journals->slice($offset, $pageSize); $journals = new LengthAwarePaginator($journals, $count, $pageSize); @@ -328,17 +328,15 @@ class BudgetController extends Controller $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); $pageSize = Preferences::get('transactionPageSize', 50)->data; $offset = ($page - 1) * $pageSize; - $journals = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); + $journals = $repository->journalsInPeriod(new Collection([$budget]), new Collection, $start, $end); $count = $journals->count(); $journals = $journals->slice($offset, $pageSize); $journals = new LengthAwarePaginator($journals, $count, $pageSize); + $subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]); $journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id); - $subTitle = trans( - 'firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)] - ); $repetition->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate); $limits = new Collection([$repetition]); diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 27e78f2142..b276785119 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -139,8 +139,8 @@ class AccountController extends Controller { - $start = session('start', Carbon::now()->startOfMonth()); - $end = session('end', Carbon::now()->endOfMonth()); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = clone session('end', Carbon::now()->endOfMonth()); // chart properties for cache: $cache = new CacheProperties(); diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index f2ed25a9b3..3fdcd96f4e 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -8,9 +8,15 @@ use FireflyIII\Generator\Chart\Budget\BudgetChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; +use Log; +use Navigation; +use Preferences; +use Response; /** * Class BudgetController @@ -43,22 +49,47 @@ class BudgetController extends Controller */ public function budget(BudgetRepositoryInterface $repository, Budget $budget) { + $first = $repository->firstUseDate($budget); + $range = Preferences::get('viewRange', '1M')->data; + $last = session('end', new Carbon); + + $cache = new CacheProperties(); + $cache->addProperty($first); + $cache->addProperty($last); + $cache->addProperty('budget'); + + if ($cache->has()) { + //return Response::json($cache->get()); + } + + $final = clone $last; + $final->addYears(2); + + $budgetCollection = new Collection([$budget]); + $last = Navigation::endOfX($last, $range, $final); // not to overshoot. + $entries = new Collection; + Log::debug('---- now at chart'); + while ($first < $last) { + + // periodspecific dates: + $currentStart = Navigation::startOfPeriod($first, $range); + $currentEnd = Navigation::endOfPeriod($first, $range); + // sub another day because reasons. + $currentEnd->subDay(); + $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd); + $entry = [$first, ($spent * -1)]; + + $entries->push($entry); + $first = Navigation::addPeriod($first, $range, 0); + } + + $data = $this->generator->budgetLimit($entries, 'month'); + $cache->store($data); + + return Response::json($data); + + /** - * // dates and times - * $first = $repository->getFirstBudgetLimitDate($budget); - * $range = Preferences::get('viewRange', '1M')->data; - * $last = session('end', new Carbon); - * - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($first); - * $cache->addProperty($last); - * $cache->addProperty('budget'); - * if ($cache->has()) { - * - * //return Response::json($cache->get()); - * } - * * $final = clone $last; * $final->addYears(2); * $last = Navigation::endOfX($last, $range, $final); @@ -96,43 +127,34 @@ class BudgetController extends Controller */ public function budgetLimit(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition) { - /** - * $start = clone $repetition->startdate; - * $end = $repetition->enddate; - * - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty('budget'); - * $cache->addProperty('limit'); - * $cache->addProperty($budget->id); - * $cache->addProperty($repetition->id); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * $set = $repository->spentPerDay($budget, $start, $end, new Collection); - * $entries = new Collection; - * $amount = $repetition->amount; - * - * // get sum (har har)! - * while ($start <= $end) { - * $formatted = $start->format('Y-m-d'); - * $sum = $set[$formatted] ?? '0'; - * - * // Sum of expenses on this day: - * $amount = round(bcadd(strval($amount), $sum), 2); - * $entries->push([clone $start, $amount]); - * $start->addDay(); - * } - * - * $data = $this->generator->budgetLimit($entries, 'monthAndDay'); - * $cache->store($data); - * - * return Response::json($data); - **/ + $start = clone $repetition->startdate; + $end = $repetition->enddate; + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('budget-limit'); + $cache->addProperty($budget->id); + $cache->addProperty($repetition->id); + if ($cache->has()) { + // return Response::json($cache->get()); + } + + $entries = new Collection; + $amount = $repetition->amount; + $budgetCollection = new Collection([$budget]); + Log::debug('amount starts ' . $amount); + while ($start <= $end) { + $spent = $repository->spentInPeriod($budgetCollection, new Collection, $start, $start); + $amount = bcadd($amount, $spent); + $entries->push([clone $start, round($amount, 2)]); + + $start->addDay(); + } + $data = $this->generator->budgetLimit($entries, 'monthAndDay'); + $cache->store($data); + + return Response::json($data); } /** @@ -146,19 +168,75 @@ class BudgetController extends Controller */ public function frontpage(BudgetRepositoryInterface $repository, ARI $accountRepository) { + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('budget'); + $cache->addProperty('all'); + if ($cache->has()) { + return Response::json($cache->get()); + } + $budgets = $repository->getActiveBudgets(); + $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); + $allEntries = new Collection; + $format = strval(trans('config.month_and_day')); + + /** @var Budget $budget */ + foreach ($budgets as $budget) { + // get relevant repetitions: + $name = $budget->name; + $reps = $repetitions->filter( + function (LimitRepetition $repetition) use ($budget, $start, $end) { + if ($repetition->startdate < $end && $repetition->enddate > $start && $repetition->budget_id === $budget->id) { + return $repetition; + } + } + ); + if ($reps->count() === 0) { + $amount = '0'; + $left = '0'; + $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); + $overspent = '0'; + $allEntries->push([$name, $left, $spent, $overspent, $amount, $spent]); + } + /** @var LimitRepetition $repetition */ + foreach ($reps as $repetition) { + $expenses = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate); + if ($reps->count() > 1) { + $name = $budget->name . ' ' . trans( + 'firefly.between_dates', + ['start' => $repetition->startdate->formatLocalized($format), 'end' => $repetition->enddate->formatLocalized($format)] + ); + } + $amount = $repetition->amount; + $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); + $spent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses; + $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0'; + $allEntries->push([$name, $left, $spent, $overspent, $amount, $spent]); + } + + } + + $list = $repository->journalsInPeriodWithoutBudget(new Collection, $start, $end); + $sum = '0'; + /** @var TransactionJournal $entry */ + foreach ($list as $entry) { + $sum = bcadd(TransactionJournal::amount($entry), $sum); + } + $allEntries->push([trans('firefly.no_budget'), '0', '0', $sum, '0', '0']); + $data = $this->generator->frontpage($allEntries); + $cache->store($data); + + return Response::json($data); + + /** - * $start = session('start', Carbon::now()->startOfMonth()); - * $end = session('end', Carbon::now()->endOfMonth()); * - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty('budget'); - * $cache->addProperty('all'); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } + * + * * * $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); * $allEntries = new Collection; @@ -379,8 +457,10 @@ class BudgetController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function year(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { + public + function year( + BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts + ) { /** * // chart properties for cache: * $cache = new CacheProperties(); diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index a118f4ed15..21b841193d 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -130,7 +130,7 @@ class Transaction extends Model if (!self::isJoined($query, 'transaction_journals')) { $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'); } - $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); + $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')); } /** diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 198c9bd9b6..897d98ed41 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -245,7 +245,7 @@ class AccountRepository implements AccountRepositoryInterface $join->on('source.transaction_journal_id', '=', 'transaction_journals.id'); } )->where('source.account_id', $account->id); - + $query->take(10); $set = $query->get(TransactionJournal::queryFields()); return $set; diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index ecc53cdeb8..baba663724 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -12,7 +12,6 @@ use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; use Log; @@ -123,6 +122,34 @@ class BudgetRepository implements BudgetRepositoryInterface // return new Carbon; // } + /** + * This method returns the oldest journal or transaction date known to this budget. + * Will cache result. + * + * @param Budget $budget + * + * @return Carbon + */ + public function firstUseDate(Budget $budget): Carbon + { + $oldest = Carbon::create()->startOfYear(); + $journal = $budget->transactionjournals()->orderBy('date', 'ASC')->first(); + if ($journal) { + $oldest = $journal->date < $oldest ? $journal->date : $oldest; + } + + $transaction = $budget + ->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id') + ->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']); + if ($transaction) { + $oldest = $transaction->date < $oldest ? $transaction->date : $oldest; + } + + return $oldest; + + } + /** * @return Collection */ @@ -140,26 +167,6 @@ class BudgetRepository implements BudgetRepositoryInterface return $set; } - /** - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection - { - $query = LimitRepetition:: - leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') - ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')) - ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) - ->where('budgets.user_id', $this->user->id); - - $set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']); - - return $set; - } - // /** // * @param Account $account // * @param Carbon $start @@ -217,18 +224,21 @@ class BudgetRepository implements BudgetRepositoryInterface // } /** + * @param Carbon $start + * @param Carbon $end + * * @return Collection */ - public function getBudgets(): Collection + public function getAllBudgetLimitRepetitions(Carbon $start, Carbon $end): Collection { - /** @var Collection $set */ - $set = $this->user->budgets()->get(); + $query = LimitRepetition:: + leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + ->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')) + ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00')) + ->where('budgets.user_id', $this->user->id); - $set = $set->sortBy( - function (Budget $budget) { - return strtolower($budget->name); - } - ); + $set = $query->get(['limit_repetitions.*', 'budget_limits.budget_id']); return $set; } @@ -505,10 +515,10 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @return Collection */ - public function getInactiveBudgets(): Collection + public function getBudgets(): Collection { /** @var Collection $set */ - $set = $this->user->budgets()->where('active', 0)->get(); + $set = $this->user->budgets()->get(); $set = $set->sortBy( function (Budget $budget) { @@ -660,7 +670,7 @@ class BudgetRepository implements BudgetRepositoryInterface // } // ) // ->whereIn('transactions.account_id', $ids) - // //->having('transaction_count', '=', 1) TODO check if this still works + // //->having('transaction_count', '=', 1) TO DO check if this still works // ->transactionTypes([TransactionType::WITHDRAWAL]) // ->first( // [ @@ -829,6 +839,23 @@ class BudgetRepository implements BudgetRepositoryInterface // return $return; // } + /** + * @return Collection + */ + public function getInactiveBudgets(): Collection + { + /** @var Collection $set */ + $set = $this->user->budgets()->where('active', 0)->get(); + + $set = $set->sortBy( + function (Budget $budget) { + return strtolower($budget->name); + } + ); + + return $set; + } + /** * @param Collection $budgets * @param Collection $accounts @@ -918,6 +945,7 @@ class BudgetRepository implements BudgetRepositoryInterface } } ); + return $set; } @@ -932,13 +960,15 @@ class BudgetRepository implements BudgetRepositoryInterface public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string { $set = $this->journalsInPeriod($budgets, $accounts, $start, $end); - Log::debug('spentInPeriod set count is ' . $set->count()); + //Log::debug('spentInPeriod set count is ' . $set->count()); $sum = '0'; /** @var TransactionJournal $journal */ foreach ($set as $journal) { $sum = bcadd($sum, TransactionJournal::amount($journal)); } + Log::debug('spentInPeriod between ' . $start->format('Y-m-d') . ' and ' . $end->format('Y-m-d') . ' is ' . $sum); + return $sum; } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 78d35421af..780a3dbf2a 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -50,6 +50,15 @@ interface BudgetRepositoryInterface */ public function find(int $budgetId): Budget; + /** + * This method returns the oldest journal or transaction date known to this budget. + * Will cache result. + * @param Budget $budget + * + * @return Carbon + */ + public function firstUseDate(Budget $budget): Carbon; + // /** // * @param Budget $budget // * @param Account $account diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index 37e9a69603..e7aab0f43f 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -20,6 +20,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\CacheProperties; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; /** * Class TransactionJournalSupport @@ -157,6 +158,8 @@ class TransactionJournalSupport extends Model } /** + * @deprecated + * * @param TransactionJournal $journal * * @return Account @@ -181,6 +184,31 @@ class TransactionJournalSupport extends Model return $account; } + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public static function destinationAccountList(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-account-list'); + if ($cache->has()) { + return $cache->get(); + } + $transactions = $journal->transactions()->where('amount', '>', 0)->with('account')->get(); + $list = new Collection; + /** @var Transaction $t */ + foreach ($transactions as $t) { + $list->push($t->account); + } + $cache->store($list); + + return $list; + } + /** * @param TransactionJournal $journal * @@ -253,6 +281,8 @@ class TransactionJournalSupport extends Model } /** + * @deprecated + * * @param TransactionJournal $journal * * @return Account @@ -277,6 +307,32 @@ class TransactionJournalSupport extends Model return $account; } + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public static function sourceAccountList(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-account-list'); + if ($cache->has()) { + return $cache->get(); + } + $transactions = $journal->transactions()->where('amount', '<', 0)->with('account')->get(); + $list = new Collection; + /** @var Transaction $t */ + foreach ($transactions as $t) { + $list->push($t->account); + } + $cache->store($list); + + return $list; + } + /** * @param TransactionJournal $journal * diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index de8ee523c3..3519b1b62f 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -5,6 +5,7 @@ namespace FireflyIII\Support; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use Log; /** * Class Navigation @@ -107,7 +108,9 @@ class Navigation $currentEnd->$function(); } if (in_array($repeatFreq, $subDay)) { + Log::debug('Before subday: ' . $currentEnd->format('Y-m-d')); $currentEnd->subDay(); + Log::debug('After subday: ' . $currentEnd->format('Y-m-d')); } return $currentEnd; diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 3218a5f843..18fd9ab2bc 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -4,7 +4,9 @@ declare(strict_types = 1); namespace FireflyIII\Support\Twig; +use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Support\CacheProperties; use Twig_Extension; use Twig_SimpleFilter; use Twig_SimpleFunction; @@ -16,6 +18,39 @@ use Twig_SimpleFunction; */ class Journal extends Twig_Extension { + /** + * @return Twig_SimpleFunction + */ + public function getDestinationAccount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'destinationAccount', function (TransactionJournal $journal) { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-account-string'); + if ($cache->has()) { + return $cache->get(); + } + + $list = TransactionJournal::destinationAccountList($journal); + $array = []; + /** @var Account $entry */ + foreach ($list as $entry) { + if ($entry->accountType->type == 'Cash account') { + $array[] = '(cash)'; + continue; + } + $array[] = '' . e($entry->name) . ''; + } + $result = join(', ', $array); + $cache->store($result); + + return $result; + + } + ); + } /** * @return array @@ -33,7 +68,8 @@ class Journal extends Twig_Extension public function getFunctions(): array { $functions = [ - $this->invalidJournal(), + $this->getSourceAccount(), + $this->getDestinationAccount(), ]; return $functions; @@ -52,15 +88,35 @@ class Journal extends Twig_Extension /** * @return Twig_SimpleFunction */ - protected function invalidJournal(): Twig_SimpleFunction + public function getSourceAccount(): Twig_SimpleFunction { return new Twig_SimpleFunction( - 'invalidJournal', function (TransactionJournal $journal): bool { - if (!isset($journal->transactions[1]) || !isset($journal->transactions[0])) { - return true; + 'sourceAccount', function (TransactionJournal $journal): string { + + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-account-string'); + if ($cache->has()) { + return $cache->get(); } - return false; + $list = TransactionJournal::sourceAccountList($journal); + $array = []; + /** @var Account $entry */ + foreach ($list as $entry) { + if ($entry->accountType->type == 'Cash account') { + $array[] = '(cash)'; + continue; + } + $array[] = '' . e($entry->name) . ''; + } + $result = join(', ', $array); + $cache->store($result); + + return $result; + + } ); } diff --git a/resources/views/list/journals.twig b/resources/views/list/journals.twig index 2e5913bf47..447a189fa5 100644 --- a/resources/views/list/journals.twig +++ b/resources/views/list/journals.twig @@ -63,18 +63,10 @@ {{ journal.date.formatLocalized(monthAndDayFormat) }} - {% if journal.source_account_type == 'Cash account' %} - (cash) - {% else %} - {{ journal.source_account_name }} - {% endif %} + {{ sourceAccount(journal)|raw }} - {% if journal.destination_account_type == 'Cash account' %} - (cash) - {% else %} - {{ journal.destination_account_name }} - {% endif %} + {{ destinationAccount(journal)|raw }} From 0460811e6c6bdce89f7bee0282d2794ea5a1300b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 6 May 2016 22:53:08 +0200 Subject: [PATCH 089/206] Fixed some more charts. --- app/Helpers/Report/BudgetReportHelper.php | 3 +- .../Controllers/Chart/BudgetController.php | 340 ++++++------------ app/Http/routes.php | 4 +- 3 files changed, 117 insertions(+), 230 deletions(-) diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 6f72f68777..ae5e9b459d 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -126,8 +126,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface $set = new Collection; /** @var Budget $budget */ foreach ($budgets as $budget) { - $expenses = [0]; // $repository->spentPerDay($budget, $start, $end, $accounts); // TODO BUDGET spentInPeriod - $total = strval(array_sum($expenses)); + $total = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end); if (bccomp($total, '0') === -1) { $set->push($budget); } diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 3fdcd96f4e..f7652a2bad 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -59,7 +59,7 @@ class BudgetController extends Controller $cache->addProperty('budget'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } $final = clone $last; @@ -76,8 +76,8 @@ class BudgetController extends Controller $currentEnd = Navigation::endOfPeriod($first, $range); // sub another day because reasons. $currentEnd->subDay(); - $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd); - $entry = [$first, ($spent * -1)]; + $spent = $repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd); + $entry = [$first, ($spent * -1)]; $entries->push($entry); $first = Navigation::addPeriod($first, $range, 0); @@ -87,33 +87,6 @@ class BudgetController extends Controller $cache->store($data); return Response::json($data); - - - /** - * $final = clone $last; - * $final->addYears(2); - * $last = Navigation::endOfX($last, $range, $final); - * $entries = new Collection; - * // get all expenses: - * $spentArray = $repository->spentPerDay($budget, $first, $last, new Collection); - * - * while ($first < $last) { - * - * // periodspecific dates: - * $currentStart = Navigation::startOfPeriod($first, $range); - * $currentEnd = Navigation::endOfPeriod($first, $range); - * $spent = $this->getSumOfRange($currentStart, $currentEnd, $spentArray); - * $entry = [$first, ($spent * -1)]; - * - * $entries->push($entry); - * $first = Navigation::addPeriod($first, $range, 0); - * } - * - * $data = $this->generator->budgetLimit($entries, 'month'); - * $cache->store($data); - * - * return Response::json($data); - * **/ } /** @@ -231,76 +204,11 @@ class BudgetController extends Controller $cache->store($data); return Response::json($data); - - - /** - * - * - * - * - * $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); - * $allEntries = new Collection; - * $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); - * $format = strval(trans('config.month_and_day')); - * - * - * // @var Budget $budget - * foreach ($budgets as $budget) { - * // we already have amount, startdate and enddate. - * // if this "is" a limit repetition (as opposed to a budget without one entirely) - * // depends on whether startdate and enddate are null. - * $name = $budget->name; - * if (is_null($budget->startdate) && is_null($budget->enddate)) { - * $currentStart = clone $start; - * $currentEnd = clone $end; - * $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); - * $amount = '0'; - * $left = '0'; - * $spent = $expenses; - * $overspent = '0'; - * } else { - * - * // update the display name if the range - * // of the limit repetition does not match - * // the session's range (for clarity). - * if ( - * ($start->format('Y-m-d') != $budget->startdate->format('Y-m-d')) - * || ($end->format('Y-m-d') != $budget->enddate->format('Y-m-d')) - * ) { - * $name .= ' ' . trans( - * 'firefly.between_dates', - * [ - * 'start' => $budget->startdate->formatLocalized($format), - * 'end' => $budget->startdate->formatLocalized($format), - * ] - * ); - * } - * $currentStart = clone $budget->startdate; - * $currentEnd = clone $budget->enddate; - * $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); - * $amount = $budget->amount; - * // smaller than 1 means spent MORE than budget allows. - * $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? '0' : bcadd($budget->amount, $expenses); - * $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcmul($amount, '-1') : $expenses; - * $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : '0'; - * } - * - * $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]); - * } - * - * $noBudgetExpenses = $repository->getWithoutBudgetSum($accounts, $start, $end); - * $allEntries->push([trans('firefly.no_budget'), '0', '0', $noBudgetExpenses, '0', '0']); - * $data = $this->generator->frontpage($allEntries); - * $cache->store($data); - * - * return Response::json($data); - **/ } /** * * @param BudgetRepositoryInterface $repository - * @param $reportType * @param Carbon $start * @param Carbon $end * @param Collection $accounts @@ -309,141 +217,122 @@ class BudgetController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function multiYear(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets) + public function multiYear(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets) { - /** - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($reportType); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($accounts); - * $cache->addProperty($budgets); - * $cache->addProperty('multiYearBudget'); - * - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * // Get the budgeted amounts for each budgets in each year. - * $budgetedSet = $repository->getBudgetedPerYear($budgets, $start, $end); - * $budgetedArray = []; - * // @var Budget $entry - * foreach ($budgetedSet as $entry) { - * $budgetedArray[$entry->id][$entry->dateFormatted] = $entry->budgeted; - * } - * - * $set = $repository->getBudgetsAndExpensesPerYear($budgets, $accounts, $start, $end); - * $entries = new Collection; - * // go by budget, not by year. - * // @var Budget $budget - * foreach ($budgets as $budget) { - * $entry = ['name' => '', 'spent' => [], 'budgeted' => []]; - * $id = $budget->id; - * $currentStart = clone $start; - * while ($currentStart < $end) { - * // fix the date: - * $currentEnd = clone $currentStart; - * $currentEnd->endOfYear(); - * - * // basic information: - * $year = $currentStart->year; - * $entry['name'] = $budget->name ?? (string)trans('firefly.no_budget'); - * $spent = 0; - * // this might be a good moment to collect no budget stuff. - * if (is_null($budget->id)) { - * // get without budget sum in range: - * $spent = $repository->getWithoutBudgetSum($accounts, $currentStart, $currentEnd) * -1; - * } else { - * if (isset($set[$id]['entries'][$year])) { - * $spent = $set[$id]['entries'][$year] * -1; - * } - * } - * - * $budgeted = $budgetedArray[$id][$year] ?? '0'; - * $entry['spent'][$year] = $spent; - * $entry['budgeted'][$year] = round($budgeted, 2); - * - * - * // jump to next year. - * $currentStart = clone $currentEnd; - * $currentStart->addDay(); - * } - * $entries->push($entry); - * } - * // generate chart with data: - * $data = $this->generator->multiYear($entries); - * $cache->store($data); - * - * return Response::json($data); - **/ + + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($accounts); + $cache->addProperty($budgets); + $cache->addProperty('multiYearBudget'); + + if ($cache->has()) { + //return Response::json($cache->get()); + } + $budgetIds = $budgets->pluck('id')->toArray(); + $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); + $budgeted = []; + $entries = new Collection; + // filter budgets once: + $repetitions = $repetitions->filter( + function (LimitRepetition $repetition) use ($budgetIds) { + if (in_array(strval($repetition->budget_id), $budgetIds)) { + return $repetition; + } + } + ); + /** @var LimitRepetition $repetition */ + foreach ($repetitions as $repetition) { + $year = $repetition->startdate->year; + if (isset($budgeted[$repetition->budget_id][$year])) { + $budgeted[$repetition->budget_id][$year] = bcadd($budgeted[$repetition->budget_id][$year], $repetition->amount); + continue; + } + $budgeted[$repetition->budget_id][$year] = $repetition->amount; + } + + foreach ($budgets as $budget) { + $currentStart = clone $start; + $entry = ['name' => $budget->name, 'spent' => [], 'budgeted' => []]; + while ($currentStart < $end) { + // fix the date: + $currentEnd = clone $currentStart; + $year = $currentStart->year; + $currentEnd->endOfYear(); + + $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd); + + // jump to next year. + $currentStart = clone $currentEnd; + $currentStart->addDay(); + + $entry['spent'][$year] = round($spent * -1, 2); + $entry['budgeted'][$year] = isset($budgeted[$budget->id][$year]) ? round($budgeted[$budget->id][$year], 2) : 0; + } + $entries->push($entry); + } + $data = $this->generator->multiYear($entries); + $cache->store($data); + + return Response::json($data); } /** - * @param Budget $budget - * @param string $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts + * @param BudgetRepositoryInterface $repository + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return \Illuminate\Http\JsonResponse */ - public function period(Budget $budget, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function period(BudgetRepositoryInterface $repository, Budget $budget, Carbon $start, Carbon $end, Collection $accounts) { - /** - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($reportType); - * $cache->addProperty($accounts); - * $cache->addProperty($budget->id); - * $cache->addProperty('budget'); - * $cache->addProperty('period'); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * // @var BudgetRepositoryInterface $repository - * $repository = app(BudgetRepositoryInterface::class); - * // loop over period, add by users range: - * $current = clone $start; - * $viewRange = Preferences::get('viewRange', '1M')->data; - * $set = new Collection; - * while ($current < $end) { - * $currentStart = clone $current; - * $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); - * - * // get all budget limits and their repetitions. - * $reps = $repository->getAllBudgetLimitRepetitions($currentStart, $currentEnd, $budget); - * $budgeted = $reps->sum('amount'); - * $perBudget = $repository->spentPerBudgetPerAccount(new Collection([$budget]), $accounts, $currentStart, $currentEnd); - * // includes null, so filter! - * $perBudget = $perBudget->filter( - * function (TransactionJournal $journal) use ($budget) { - * if (intval($journal->budget_id) === $budget->id) { - * return $journal; - * } - * } - * ); - * - * - * $spent = $perBudget->sum('spent'); - * - * $entry = [ - * 'date' => clone $currentStart, - * 'budgeted' => $budgeted, - * 'spent' => $spent, - * ]; - * $set->push($entry); - * $currentEnd->addDay(); - * $current = clone $currentEnd; - * } - * $data = $this->generator->period($set, $viewRange); - * $cache->store($data); - * - * return Response::json($data); - */ + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($accounts); + $cache->addProperty($budget->id); + $cache->addProperty('budget'); + $cache->addProperty('period'); + if ($cache->has()) { + //return Response::json($cache->get()); + } + // loop over period, add by users range: + $current = clone $start; + $viewRange = Preferences::get('viewRange', '1M')->data; + $set = new Collection; + $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); + + + while ($current < $end) { + $currentStart = clone $current; + $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); + $reps = $repetitions->filter( + function (LimitRepetition $repetition) use ($budget, $currentStart) { + if ($repetition->budget_id === $budget->id && $repetition->startdate == $currentStart) { + return $repetition; + } + } + ); + $budgeted = $reps->sum('amount'); + $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd); + $entry = [ + 'date' => clone $currentStart, + 'budgeted' => $budgeted, + 'spent' => $spent, + ]; + $set->push($entry); + $currentEnd->addDay(); + $current = clone $currentEnd; + + } + $data = $this->generator->period($set, $viewRange); + $cache->store($data); + + return Response::json($data); } @@ -457,10 +346,9 @@ class BudgetController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public - function year( - BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts - ) { + public function year(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + /** * // chart properties for cache: * $cache = new CacheProperties(); diff --git a/app/Http/routes.php b/app/Http/routes.php index d811da5f7f..55a53c7e17 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -201,8 +201,8 @@ Route::group( Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']); // this chart is used in reports: - Route::get('/chart/budget/multi-year/{reportType}/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']); - Route::get('/chart/budget/period/{budget}/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@period']); + Route::get('/chart/budget/multi-year/default/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']); + Route::get('/chart/budget/period/{budget}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@period']); Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']); Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']); From 3588bd881c4d0bbd43e9f4b0a67f673685e2b574 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 6 May 2016 22:54:36 +0200 Subject: [PATCH 090/206] More chart cleanup. --- .../Controllers/Chart/BudgetController.php | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index f7652a2bad..162ca52394 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -333,70 +333,5 @@ class BudgetController extends Controller $cache->store($data); return Response::json($data); - - } - - /** - * - * @param BudgetRepositoryInterface $repository - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return \Illuminate\Http\JsonResponse - */ - public function year(BudgetRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - - /** - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($reportType); - * $cache->addProperty($accounts); - * $cache->addProperty('budget'); - * $cache->addProperty('year'); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * $budgetInformation = $repository->getBudgetsAndExpensesPerMonth($accounts, $start, $end); - * $budgets = new Collection; - * $entries = new Collection; - * - * // @var array $row - * foreach ($budgetInformation as $row) { - * $budgets->push($row['budget']); - * } - * while ($start < $end) { - * // month is the current end of the period: - * $month = clone $start; - * $month->endOfMonth(); - * $row = [clone $start]; - * $dateFormatted = $start->format('Y-m'); - * - * // each budget, check if there is an entry for this month: - * // @var array $row - * foreach ($budgetInformation as $budgetRow) { - * $spent = 0; // nothing spent. - * if (isset($budgetRow['entries'][$dateFormatted])) { - * $spent = $budgetRow['entries'][$dateFormatted] * -1; // to fit array - * } - * $row[] = $spent; - * } - * - * // add "no budget" thing. - * $row[] = round(bcmul($repository->getWithoutBudgetSum($accounts, $start, $month), '-1'), 4); - * - * $entries->push($row); - * $start->endOfMonth()->addDay(); - * } - * $data = $this->generator->year($budgets, $entries); - * $cache->store($data); - * - * return Response::json($data); - */ } } From 98e683329e49e9e2f95ac3e68ec36f5afae99b20 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 8 May 2016 13:45:23 +0200 Subject: [PATCH 091/206] New stuff for categories and transactions. --- app/Helpers/Csv/Converter/CategoryId.php | 6 +- app/Helpers/Csv/Converter/CategoryName.php | 6 +- app/Helpers/Report/BudgetReportHelper.php | 2 +- .../Report/BudgetReportHelperInterface.php | 9 + app/Helpers/Report/ReportHelper.php | 42 +- app/Http/Controllers/CategoryController.php | 83 +- .../Controllers/Chart/CategoryController.php | 57 +- app/Http/Controllers/Controller.php | 2 +- .../Controllers/Popup/ReportController.php | 8 +- app/Http/Controllers/ReportController.php | 2 +- app/Providers/CategoryServiceProvider.php | 14 - .../Category/CategoryRepository.php | 1005 +++++++++++++---- .../Category/CategoryRepositoryInterface.php | 299 ++++- .../Category/SingleCategoryRepository.php | 322 ------ .../SingleCategoryRepositoryInterface.php | 138 --- 15 files changed, 1146 insertions(+), 849 deletions(-) delete mode 100644 app/Repositories/Category/SingleCategoryRepository.php delete mode 100644 app/Repositories/Category/SingleCategoryRepositoryInterface.php diff --git a/app/Helpers/Csv/Converter/CategoryId.php b/app/Helpers/Csv/Converter/CategoryId.php index b28b62a224..0e9266f047 100644 --- a/app/Helpers/Csv/Converter/CategoryId.php +++ b/app/Helpers/Csv/Converter/CategoryId.php @@ -3,7 +3,7 @@ declare(strict_types = 1); namespace FireflyIII\Helpers\Csv\Converter; use FireflyIII\Models\Category; -use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; /** * Class CategoryId @@ -18,8 +18,8 @@ class CategoryId extends BasicConverter implements ConverterInterface */ public function convert(): Category { - /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app(SingleCategoryRepositoryInterface::class); + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); $value = isset($this->mapped[$this->index][$this->value]) ? $this->mapped[$this->index][$this->value] : $this->value; $category = $repository->find($value); diff --git a/app/Helpers/Csv/Converter/CategoryName.php b/app/Helpers/Csv/Converter/CategoryName.php index c987bc3192..971cc745c1 100644 --- a/app/Helpers/Csv/Converter/CategoryName.php +++ b/app/Helpers/Csv/Converter/CategoryName.php @@ -4,7 +4,7 @@ namespace FireflyIII\Helpers\Csv\Converter; use Auth; use FireflyIII\Models\Category; -use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; /** * Class CategoryName @@ -19,8 +19,8 @@ class CategoryName extends BasicConverter implements ConverterInterface */ public function convert(): Category { - /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app(SingleCategoryRepositoryInterface::class); + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); // is mapped? Then it's easy! if (isset($this->mapped[$this->index][$this->value])) { diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index ae5e9b459d..68449f2b39 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -141,7 +141,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface } /** - * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay + * Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay * and sum up everything in the array in the given range. * * @param Carbon $start diff --git a/app/Helpers/Report/BudgetReportHelperInterface.php b/app/Helpers/Report/BudgetReportHelperInterface.php index 6bc376f2e4..9858655153 100644 --- a/app/Helpers/Report/BudgetReportHelperInterface.php +++ b/app/Helpers/Report/BudgetReportHelperInterface.php @@ -39,4 +39,13 @@ interface BudgetReportHelperInterface * @return Collection */ public function getBudgetsWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection; + + /** + * @param $start + * @param $end + * @param $accounts + * + * @return Collection + */ + public function getCategoriesWithTransactions($start, $end, $accounts): Collection; } diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index f3d942c580..ec79046336 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -103,35 +103,35 @@ class ReportHelper implements ReportHelperInterface } /** + * Find all transactions and IF we have spent money in them + * with either transactions or journals. + * * @param Carbon $start * @param Carbon $end * @param Collection $accounts * * @return Collection */ - public function getCategoriesWithExpenses(Carbon $start, Carbon $end, Collection $accounts): Collection + public function getCategoriesWithTransactions(Carbon $start, Carbon $end, Collection $accounts): Collection { /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); - $collection = $repository->earnedForAccountsPerMonth($accounts, $start, $end); - $second = $repository->spentForAccountsPerMonth($accounts, $start, $end); - $collection = $collection->merge($second); - $array = []; - /** @var Category $category */ - foreach ($collection as $category) { - $id = $category->id; - $array[$id] = $category; - + $categories = $repository->getCategories(); + $return = new Collection; + foreach ($categories as $category) { + $lastUseDate = $repository->lastUseDate($category, $accounts); + if ($lastUseDate >= $start && $lastUseDate <= $end) { + $return->push($category); + } } - $set = new Collection($array); - $set = $set->sortBy( + $return = $return->sortBy( function (Category $category) { return $category->name; } ); - return $set; + return $return; } /** @@ -144,15 +144,15 @@ class ReportHelper implements ReportHelperInterface public function getCategoryReport(Carbon $start, Carbon $end, Collection $accounts): CategoryCollection { $object = new CategoryCollection; - - /** - * GET CATEGORIES: - */ - /** @var \FireflyIII\Repositories\Category\CategoryRepositoryInterface $repository */ + /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); + $categories = $repository->getCategories(); - $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); - foreach ($set as $category) { + /** @var Category $category */ + foreach ($categories as $category) { + $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end); + // CategoryCollection expects the amount in $spent: + $category->spent = $spent; $object->addCategory($category); } @@ -312,7 +312,7 @@ class ReportHelper implements ReportHelperInterface } /** - * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay + * Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay * and sum up everything in the array in the given range. * * @param Carbon $start diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 896451a850..dc23f9614d 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -3,9 +3,9 @@ use Auth; use Carbon\Carbon; use FireflyIII\Http\Requests\CategoryFormRequest; +use FireflyIII\Models\Account; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; -use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface as SCRI; use FireflyIII\Support\CacheProperties; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -69,12 +69,12 @@ class CategoryController extends Controller } /** - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @return \Illuminate\Http\RedirectResponse */ - public function destroy(SCRI $repository, Category $category) + public function destroy(CRI $repository, Category $category) { $name = $category->name; @@ -108,18 +108,17 @@ class CategoryController extends Controller } /** - * @param CRI $repository - * @param SCRI $singleRepository + * @param CRI $repository * * @return \Illuminate\View\View */ - public function index(CRI $repository, SCRI $singleRepository) + public function index(CRI $repository) { $categories = $repository->getCategories(); $categories->each( - function (Category $category) use ($singleRepository) { - $category->lastActivity = $singleRepository->getLatestActivity($category); + function (Category $category) use ($repository) { + $category->lastActivity = $repository->lastUseDate($category, new Collection); } ); @@ -137,7 +136,7 @@ class CategoryController extends Controller $start = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ $end = session('end', Carbon::now()->startOfMonth()); - $list = $repository->listNoCategory($start, $end); + $list = $repository->journalsInPeriodWithoutCategory(new Collection(), $start, $end); $subTitle = trans( 'firefly.without_category_between', ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] @@ -147,26 +146,24 @@ class CategoryController extends Controller } /** - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @return \Illuminate\View\View */ - public function show(SCRI $repository, Category $category) + public function show(CRI $repository, Category $category) { $hideCategory = true; // used in list. $pageSize = Preferences::get('transactionPageSize', 50)->data; $page = intval(Input::get('page')); - $set = $repository->getJournals($category, $page, $pageSize); - $count = $repository->countJournals($category); - $subTitle = $category->name; - $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); + $journals = $repository->getJournals($category, $page, $pageSize); $journals->setPath('categories/show/' . $category->id); // list of ranges for list of periods: // oldest transaction in category: - $start = $repository->getFirstActivityDate($category); + //$start = $repository->getFirstActivityDate($category); + $start = $repository->firstUseDate($category, new Account); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($start, $range); $end = Navigation::endOfX(new Carbon, $range); @@ -179,26 +176,22 @@ class CategoryController extends Controller $cache->addProperty('category-show'); $cache->addProperty($category->id); - // get all spent and earned data: - // get amount earned in period, grouped by day. - $spentArray = $repository->spentPerDay($category, $start, $end, new Collection); - $earnedArray = $repository->earnedPerDay($category, $start, $end, new Collection); if ($cache->has()) { - $entries = $cache->get(); - - return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle')); + //$entries = $cache->get(); + //return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle')); } + + $categoryCollection = new Collection([$category]); + $empty = new Collection; while ($end >= $start) { $end = Navigation::startOfPeriod($end, $range); $currentEnd = Navigation::endOfPeriod($end, $range); - - // get data from spentArray: - $spent = $this->getSumOfRange($end, $currentEnd, $spentArray); - $earned = $this->getSumOfRange($end, $currentEnd, $earnedArray); - $dateStr = $end->format('Y-m-d'); - $dateName = Navigation::periodShow($end, $range); + $spent = $repository->spentInPeriod($categoryCollection, $empty, $end, $currentEnd); + $earned = $repository->earnedInPeriod($categoryCollection, $empty, $end, $currentEnd); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); $entries->push([$dateStr, $dateName, $spent, $earned]); $end = Navigation::subtractPeriod($end, $range, 1); @@ -210,28 +203,28 @@ class CategoryController extends Controller } /** - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @param $date * * @return \Illuminate\View\View */ - public function showWithDate(SCRI $repository, Category $category, string $date) + public function showWithDate(CRI $repository, Category $category, string $date) { - $carbon = new Carbon($date); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); - $subTitle = $category->name; - + $carbon = new Carbon($date); + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($carbon, $range); + $end = Navigation::endOfPeriod($carbon, $range); + $subTitle = $category->name; $hideCategory = true; // used in list. $page = intval(Input::get('page')); $pageSize = Preferences::get('transactionPageSize', 50)->data; - - $set = $repository->getJournalsInRange($category, $start, $end, $page, $pageSize); - $count = $repository->countJournals($category, $start, $end); - $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); + $offset = ($page - 1) * $pageSize; + $set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); + $count = $set->count(); + $subSet = $set->splice($offset, $pageSize); + $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); $journals->setPath('categories/show/' . $category->id . '/' . $date); return view('categories.show_with_date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon')); @@ -239,11 +232,11 @@ class CategoryController extends Controller /** * @param CategoryFormRequest $request - * @param SCRI $repository + * @param CRI $repository * * @return \Illuminate\Http\RedirectResponse */ - public function store(CategoryFormRequest $request, SCRI $repository) + public function store(CategoryFormRequest $request, CRI $repository) { $categoryData = [ 'name' => $request->input('name'), @@ -266,12 +259,12 @@ class CategoryController extends Controller /** * @param CategoryFormRequest $request - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @return \Illuminate\Http\RedirectResponse */ - public function update(CategoryFormRequest $request, SCRI $repository, Category $category) + public function update(CategoryFormRequest $request, CRI $repository, Category $category) { $categoryData = [ 'name' => $request->input('name'), diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index a84fccd578..b7c974efab 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -10,8 +10,6 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; -use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface; -use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface as SCRI; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Navigation; @@ -47,13 +45,14 @@ class CategoryController extends Controller /** * Show an overview for a category for all time, per month/week/year. * - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @return \Symfony\Component\HttpFoundation\Response */ - public function all(SCRI $repository, Category $category) + public function all(CRI $repository, Category $category) { + /** // oldest transaction in category: $start = $repository->getFirstActivityDate($category); $range = Preferences::get('viewRange', '1M')->data; @@ -87,21 +86,24 @@ class CategoryController extends Controller $cache->store($data); return Response::json($data); + * **/ } /** - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @return \Symfony\Component\HttpFoundation\Response */ - public function currentPeriod(SCRI $repository, Category $category) + public function currentPeriod(CRI $repository, Category $category) { + /** $start = clone session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); $data = $this->makePeriodChart($repository, $category, $start, $end); return Response::json($data); + * **/ } /** @@ -118,6 +120,7 @@ class CategoryController extends Controller */ public function earnedInPeriod(CRI $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { + /** $cache = new CacheProperties; // chart properties for cache: $cache->addProperty($start); $cache->addProperty($end); @@ -140,7 +143,7 @@ class CategoryController extends Controller $cache->store($data); return $data; - + **/ } /** @@ -154,7 +157,7 @@ class CategoryController extends Controller */ public function frontpage(CRI $repository, ARI $accountRepository) { - + /** $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); @@ -184,7 +187,7 @@ class CategoryController extends Controller $cache->store($data); return Response::json($data); - + **/ } /** @@ -198,8 +201,9 @@ class CategoryController extends Controller */ public function multiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) { - /** @var CRI $repository */ - $repository = app(CRI::class); + /** + // /** @var CRI $repository + // $repository = app(CRI::class); // chart properties for cache: $cache = new CacheProperties(); @@ -217,7 +221,7 @@ class CategoryController extends Controller $entries = new Collection; $set = $repository->listMultiYear($categories, $accounts, $start, $end); - /** @var Category $category */ + /** @var Category $category foreach ($categories as $category) { $entry = ['name' => '', 'spent' => [], 'earned' => []]; @@ -268,6 +272,8 @@ class CategoryController extends Controller $cache->store($data); return Response::json($data); + * + */ } @@ -282,6 +288,7 @@ class CategoryController extends Controller */ public function period(Category $category, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { + /** // chart properties for cache: $cache = new CacheProperties(); $cache->addProperty($start); @@ -295,8 +302,8 @@ class CategoryController extends Controller return Response::json($cache->get()); } - /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app(SingleCategoryRepositoryInterface::class); + /** @var CategoryRepositoryInterface $repository + $repository = app(CategoryRepositoryInterface::class); // loop over period, add by users range: $current = clone $start; $viewRange = Preferences::get('viewRange', '1M')->data; @@ -324,19 +331,21 @@ class CategoryController extends Controller $cache->store($data); return Response::json($data); + * **/ } /** - * @param SCRI $repository + * @param CRI $repository * @param Category $category * * @param $date * * @return \Symfony\Component\HttpFoundation\Response */ - public function specificPeriod(SCRI $repository, Category $category, $date) + public function specificPeriod(CRI $repository, Category $category, $date) { + /** $carbon = new Carbon($date); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($carbon, $range); @@ -345,7 +354,7 @@ class CategoryController extends Controller return Response::json($data); - + **/ } /** @@ -363,6 +372,7 @@ class CategoryController extends Controller */ public function spentInPeriod(CRI $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts) { + /** $cache = new CacheProperties; // chart properties for cache: $cache->addProperty($start); $cache->addProperty($end); @@ -387,6 +397,7 @@ class CategoryController extends Controller $cache->store($data); return $data; + * */ } /** @@ -399,6 +410,7 @@ class CategoryController extends Controller */ private function filterCollection(Carbon $start, Carbon $end, Collection $set, Collection $categories): Collection { + /** $entries = new Collection; while ($start < $end) { // filter the set: @@ -408,7 +420,7 @@ class CategoryController extends Controller return $category->dateFormatted == $start->format('Y-m'); } ); - /** @var Category $category */ + /** @var Category $category foreach ($categories as $category) { // check for each category if its in the current set. $entry = $currentSet->filter( // if its in there, use the value. function (Category $cat) use ($category) { @@ -426,6 +438,7 @@ class CategoryController extends Controller } return $entries; + * */ } /** @@ -437,6 +450,7 @@ class CategoryController extends Controller */ private function invertSelection(Collection $entries): Collection { + /** $result = new Collection; foreach ($entries as $entry) { $new = [$entry[0]]; @@ -448,19 +462,21 @@ class CategoryController extends Controller } return $result; + * **/ } /** - * @param SCRI $repository + * @param CRI $repository * @param Category $category * @param Carbon $start * @param Carbon $end * * @return array */ - private function makePeriodChart(SCRI $repository, Category $category, Carbon $start, Carbon $end) + private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end) { + /** // chart properties for cache: $cache = new CacheProperties; $cache->addProperty($start); @@ -490,6 +506,7 @@ class CategoryController extends Controller $cache->store($data); return $data; + */ } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 669caf9485..d9947d723b 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -73,7 +73,7 @@ class Controller extends BaseController } /** - * Take the array as returned by SingleCategoryRepositoryInterface::spentPerDay and SingleCategoryRepositoryInterface::earnedByDay + * Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay * and sum up everything in the array in the given range. * * @param Carbon $start diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index cbf4bc3010..07280a4a3e 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -18,7 +18,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\Binder\AccountList; use Illuminate\Http\Request; use Illuminate\Support\Collection; @@ -160,10 +160,10 @@ class ReportController extends Controller */ private function categoryEntry(array $attributes): string { - /** @var SingleCategoryRepositoryInterface $repository */ - $repository = app(SingleCategoryRepositoryInterface::class); + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); $category = $repository->find(intval($attributes['categoryId'])); - $journals = $repository->getJournalsForAccountsInRange($category, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $journals = $repository->journalsInPeriod(new Collection([$category]), $attributes['accounts'], [], $attributes['startDate'], $attributes['endDate']); $view = view('popup.report.category-entry', compact('journals', 'category'))->render(); return $view; diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index ce34559df8..21e76eef1d 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -317,7 +317,7 @@ class ReportController extends Controller $budgets = $this->budgetHelper->getBudgetsWithExpenses($start, $end, $accounts); // find the categories we've spent money on this period with these accounts: - $categories = $this->helper->getCategoriesWithExpenses($start, $end, $accounts); + $categories = $this->helper->getCategoriesWithTransactions($start, $end, $accounts); Session::flash('gaEventCategory', 'report'); Session::flash('gaEventAction', 'year'); diff --git a/app/Providers/CategoryServiceProvider.php b/app/Providers/CategoryServiceProvider.php index 7da232890c..0278b3f472 100644 --- a/app/Providers/CategoryServiceProvider.php +++ b/app/Providers/CategoryServiceProvider.php @@ -44,19 +44,5 @@ class CategoryServiceProvider extends ServiceProvider } ); - $this->app->bind( - 'FireflyIII\Repositories\Category\SingleCategoryRepositoryInterface', - function (Application $app, array $arguments) { - if (!isset($arguments[0]) && $app->auth->check()) { - return app('FireflyIII\Repositories\Category\SingleCategoryRepository', [$app->auth->user()]); - } - if (!isset($arguments[0]) && !$app->auth->check()) { - throw new FireflyException('There is no user present.'); - } - - return app('FireflyIII\Repositories\Category\SingleCategoryRepository', $arguments); - } - ); - } } diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 0b8209b76d..2bfb97930d 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -4,11 +4,11 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Category; use Carbon\Carbon; -use DB; use FireflyIII\Models\Category; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; -use Illuminate\Database\Query\JoinClause; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; /** @@ -18,8 +18,8 @@ use Illuminate\Support\Collection; */ class CategoryRepository implements CategoryRepositoryInterface { - const SPENT = 1; - const EARNED = 2; + // const SPENT = 1; + // const EARNED = 2; /** @var User */ @@ -35,54 +35,577 @@ class CategoryRepository implements CategoryRepositoryInterface $this->user = $user; } + // /** + // * Returns a collection of Categories appended with the amount of money that has been earned + // * in these categories, based on the $accounts involved, in period X, grouped per month. + // * The amount earned in category X in period X is saved in field "earned". + // * + // * @param $accounts + // * @param $start + // * @param $end + // * + // * @return Collection + // */ + // public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // + // $collection = $this->user->categories() + // ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + // ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + // ->leftJoin( + // 'transactions AS t_src', function (JoinClause $join) { + // $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); + // } + // ) + // ->leftJoin( + // 'transactions AS t_dest', function (JoinClause $join) { + // $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); + // } + // ) + // ->whereIn('t_dest.account_id', $accounts->pluck('id')->toArray())// to these accounts (earned) + // ->whereNotIn('t_src.account_id', $accounts->pluck('id')->toArray())//-- but not from these accounts + // ->whereIn( + // 'transaction_types.type', [TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] + // ) + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + // ->groupBy('categories.id') + // ->groupBy('dateFormatted') + // ->get( + // [ + // 'categories.*', + // DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), + // DB::raw('SUM(`t_dest`.`amount`) AS `earned`'), + // ] + // ); + // + // return $collection; + // + // + // } + + // /** + // * @param Category $category + // * @param Carbon|null $start + // * @param Carbon|null $end + // * + // * @return int + // */ + // public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int + // { + // $query = $category->transactionjournals(); + // if (!is_null($start)) { + // $query->after($start); + // } + // if (!is_null($end)) { + // $query->before($end); + // } + // + // return $query->count(); + // + // } + + // /** + // * This method returns a very special collection for each category: + // * + // * category, year, expense/earned, amount + // * + // * categories can be duplicated. + // * + // * @param Collection $categories + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // + // $set = $this->user->categories() + // ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'category_transaction_journal.transaction_journal_id') + // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + // ->whereIn('transaction_types.type', [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL]) + // ->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()) + // ->whereIn('categories.id', $categories->pluck('id')->toArray()) + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + // ->groupBy('categories.id') + // ->groupBy('transaction_types.type') + // ->groupBy('dateFormatted') + // ->get( + // [ + // 'categories.*', + // DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y") as `dateFormatted`'), + // 'transaction_types.type', + // DB::raw('SUM(`amount`) as `sum`'), + // ] + // ); + // + // return $set; + // + // } + + // /** + // * Returns a list of transaction journals in the range (all types, all accounts) that have no category + // * associated to them. + // * + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function listNoCategory(Carbon $start, Carbon $end): Collection + // { + // return $this->user + // ->transactionjournals() + // ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->whereNull('category_transaction_journal.id') + // ->before($end) + // ->after($start) + // ->orderBy('transaction_journals.date', 'DESC') + // ->orderBy('transaction_journals.order', 'ASC') + // ->orderBy('transaction_journals.id', 'DESC') + // ->get(['transaction_journals.*']); + // } + + // /** + // * Returns a collection of Categories appended with the amount of money that has been spent + // * in these categories, based on the $accounts involved, in period X, grouped per month. + // * The amount spent in category X in period X is saved in field "spent". + // * + // * @param $accounts + // * @param $start + // * @param $end + // * + // * @return Collection + // */ + // public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // $accountIds = $accounts->pluck('id')->toArray(); + // $query = $this->user->categories() + // ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + // ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + // ->leftJoin( + // 'transactions AS t_src', function (JoinClause $join) { + // $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); + // } + // ) + // ->leftJoin( + // 'transactions AS t_dest', function (JoinClause $join) { + // $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); + // } + // ) + // ->whereIn( + // 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] + // )// spent on these things. + // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + // ->groupBy('categories.id') + // ->groupBy('dateFormatted'); + // + // if (count($accountIds) > 0) { + // $query->whereIn('t_src.account_id', $accountIds)// from these accounts (spent) + // ->whereNotIn('t_dest.account_id', $accountIds);//-- but not from these accounts (spent internally) + // } + // + // $collection = $query->get( + // [ + // 'categories.*', + // DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), + // DB::raw('SUM(`t_src`.`amount`) AS `spent`'), + // ] + // ); + // + // return $collection; + // } + + // /** + // * Returns the total amount of money related to transactions without any category connected to + // * it. Returns either the earned amount. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return string + // */ + // public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string + // { + // return $this->sumNoCategory($accounts, $start, $end, self::EARNED); + // } + + // /** + // * Returns the total amount of money related to transactions without any category connected to + // * it. Returns either the spent amount. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return string + // */ + // public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string + // { + // $sum = $this->sumNoCategory($accounts, $start, $end, self::SPENT); + // if (is_null($sum)) { + // return '0'; + // } + // + // return $sum; + // } + + // /** + // * Returns the total amount of money related to transactions without any category connected to + // * it. Returns either the earned or the spent amount. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * @param int $group + // * + // * @return string + // */ + // protected function sumNoCategory(Collection $accounts, Carbon $start, Carbon $end, $group = self::EARNED) + // { + // $accountIds = $accounts->pluck('id')->toArray(); + // if ($group == self::EARNED) { + // $types = [TransactionType::DEPOSIT]; + // } else { + // $types = [TransactionType::WITHDRAWAL]; + // } + // + // // is withdrawal or transfer AND account_from is in the list of $accounts + // $query = $this->user + // ->transactionjournals() + // ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + // ->whereNull('category_transaction_journal.id') + // ->before($end) + // ->after($start) + // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + // ->having('transaction_count', '=', 1) + // ->transactionTypes($types); + // + // if (count($accountIds) > 0) { + // $query->whereIn('transactions.account_id', $accountIds); + // } + // + // + // $single = $query->first( + // [ + // DB::raw('SUM(`transactions`.`amount`) as `sum`'), + // DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), + // ] + // ); + // if (!is_null($single)) { + // return $single->sum; + // } + // + // return '0'; + // + // } + /** - * Returns a collection of Categories appended with the amount of money that has been earned - * in these categories, based on the $accounts involved, in period X, grouped per month. - * The amount earned in category X in period X is saved in field "earned". + * @param Category $category * - * @param $accounts - * @param $start - * @param $end - * - * @return Collection + * @return bool */ - public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection + public function destroy(Category $category): bool { + $category->delete(); - $collection = $this->user->categories() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions AS t_src', function (JoinClause $join) { - $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_dest', function (JoinClause $join) { - $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); - } - ) - ->whereIn('t_dest.account_id', $accounts->pluck('id')->toArray())// to these accounts (earned) - ->whereNotIn('t_src.account_id', $accounts->pluck('id')->toArray())//-- but not from these accounts - ->whereIn( - 'transaction_types.type', [TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] - ) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') - ->groupBy('dateFormatted') - ->get( - [ - 'categories.*', - DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), - DB::raw('SUM(`t_dest`.`amount`) AS `earned`'), - ] - ); + return true; + } - return $collection; + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category + // * from all the users $accounts. + // * + // * @param Category $category + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return array + // */ + // public function earnedPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array + // { + // /** @var Collection $query */ + // $query = $category->transactionjournals() + // ->expanded() + // ->transactionTypes([TransactionType::DEPOSIT]) + // ->before($end) + // ->after($start) + // ->groupBy('transaction_journals.date'); + // + // $query->leftJoin( + // 'transactions as destination', function (JoinClause $join) { + // $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); + // } + // ); + // + // + // if ($accounts->count() > 0) { + // $ids = $accounts->pluck('id')->toArray(); + // $query->whereIn('destination.account.id', $ids); + // } + // + // $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`destination`.`amount`) AS `sum`')]); + // + // $return = []; + // foreach ($result->toArray() as $entry) { + // $return[$entry['dateFormatted']] = $entry['sum']; + // } + // + // return $return; + // } + /** + * @param Collection $categories + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string + { + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); + $sum = '0'; + foreach ($journals as $journal) { + $sum = bcadd(TransactionJournal::amount($journal), $sum); + } + return $sum; + } + + /** + * Find a category + * + * @param int $categoryId + * + * @return Category + */ + public function find(int $categoryId) : Category + { + $category = $this->user->categories()->find($categoryId); + if (is_null($category)) { + $category = new Category; + } + + return $category; + } + + // /** + // * @param Category $category + // * + // * @return Carbon + // */ + // public function getFirstActivityDate(Category $category): Carbon + // { + // /** @var TransactionJournal $first */ + // $first = $category->transactionjournals()->orderBy('date', 'ASC')->first(); + // if ($first) { + // return $first->date; + // } + // + // return new Carbon; + // + // } + + // /** + // * @param Category $category + // * @param int $page + // * @param int $pageSize + // * + // * @return Collection + // */ + // public function getJournals(Category $category, int $page, int $pageSize = 50): Collection + // { + // $offset = $page > 0 ? $page * $pageSize : 0; + // + // return $category->transactionjournals()->expanded()->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); + // + // } + // + // /** + // * @param Category $category + // * @param Collection $accounts + // * + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection + // { + // $ids = $accounts->pluck('id')->toArray(); + // + // return $category->transactionjournals() + // ->after($start) + // ->before($end) + // ->expanded() + // ->whereIn('source_account.id', $ids) + // ->whereNotIn('destination_account.id', $ids) + // ->get(TransactionJournal::queryFields()); + // } + + // /** + // * @param Category $category + // * @param Carbon $start + // * @param Carbon $end + // * @param int $page + // * @param int $pageSize + // * + // * + // * @return Collection + // */ + // public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection + // { + // $offset = $page > 0 ? $page * $pageSize : 0; + // + // return $category->transactionjournals() + // ->after($start) + // ->before($end) + // ->expanded() + // ->take($pageSize) + // ->offset($offset) + // ->get(TransactionJournal::queryFields()); + // } + + // /** + // * @param Category $category + // * + // * @return Carbon + // */ + // public function getLatestActivity(Category $category): Carbon + // { + // $first = new Carbon('1900-01-01'); + // $second = new Carbon('1900-01-01'); + // $latest = $category->transactionjournals() + // ->orderBy('transaction_journals.date', 'DESC') + // ->orderBy('transaction_journals.order', 'ASC') + // ->orderBy('transaction_journals.id', 'DESC') + // ->first(); + // if ($latest) { + // $first = $latest->date; + // } + // + // // could also be a transaction, nowadays: + // $latestTransaction = $category->transactions() + // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + // ->orderBy('transaction_journals.date', 'DESC') + // ->orderBy('transaction_journals.order', 'ASC') + // ->orderBy('transaction_journals.id', 'DESC') + // ->first(['transactions.*', 'transaction_journals.date']); + // if ($latestTransaction) { + // $second = new Carbon($latestTransaction->date); + // } + // if ($first > $second) { + // return $first; + // } + // + // return $second; + // } + + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $category + // * from all the users accounts. + // * + // * @param Category $category + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return array + // */ + // public function spentPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array + // { + // /** @var Collection $query */ + // $query = $category->transactionjournals() + // ->expanded() + // ->transactionTypes([TransactionType::WITHDRAWAL]) + // ->before($end) + // ->after($start) + // ->groupBy('transaction_journals.date'); + // $query->leftJoin( + // 'transactions as source', function (JoinClause $join) { + // $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); + // } + // ); + // + // if ($accounts->count() > 0) { + // $ids = $accounts->pluck('id')->toArray(); + // $query->whereIn('source.account_id', $ids); + // } + // + // $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`source`.`amount`) AS `sum`')]); + // + // $return = []; + // foreach ($result->toArray() as $entry) { + // $return[$entry['dateFormatted']] = $entry['sum']; + // } + // + // return $return; + // } + + /** + * @param Category $category + * @param Collection $accounts + * + * @return Carbon + */ + public function firstUseDate(Category $category, Collection $accounts): Carbon + { + $first = null; + + /** @var TransactionJournal $first */ + $firstJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC'); + + if ($accounts->count() > 0) { + // filter journals: + $ids = $accounts->pluck('id')->toArray(); + $firstJournalQuery->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); + $firstJournalQuery->whereIn('t.account_id', $ids); + } + + $firstJournal = $firstJournalQuery->first(['transaction_journals.*']); + + if ($firstJournal) { + $first = $firstJournal->date; + } + + // check transactions: + + $firstTransactionQuery = $category->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'ASC'); + if ($accounts->count() > 0) { + // filter journals: + $ids = $accounts->pluck('id')->toArray(); + $firstTransactionQuery->whereIn('transactions.account_id', $ids); + } + + $firstTransaction = $firstJournalQuery->first(['transaction_journals.*']); + + if (!is_null($firstTransaction) && !is_null($first) && $firstTransaction->date < $first) { + $first = $firstTransaction->date; + } + + return $first; } /** @@ -104,205 +627,249 @@ class CategoryRepository implements CategoryRepositoryInterface } /** - * This method returns a very special collection for each category: + * @param Category $category + * @param int $page + * @param int $pageSize * - * category, year, expense/earned, amount + * @return LengthAwarePaginator + */ + public function getJournals(Category $category, int $page, int $pageSize): LengthAwarePaginator + { + $complete = new Collection; + // first collect actual transaction journals (fairly easy) + $query = $this->user->transactionjournals()->expanded(); + $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $query->where('category_transaction_journal.category_id', $category->id); + $first = $query->get(TransactionJournal::queryFields()); + + // then collection transactions (harder) + $query = $this->user->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id'); + $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); + $query->where('category_transaction.category_id', $category->id); + $second = $query->get('transaction_journals.*'); + + + $complete = $complete->merge($first); + $complete = $complete->merge($second); + + return $complete; + } + + /** + * Get all transactions in a category in a range. * - * categories can be duplicated. + * @param Collection $categories + * @param Collection $accounts + * @param array $types + * @param Carbon $start + * @param Carbon $end * + * @return Collection + */ + public function journalsInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): Collection + { + $complete = new Collection; + // first collect actual transaction journals (fairly easy) + $query = $this->user->transactionjournals()->expanded(); + + if ($end > $start) { + $query->before($end)->after($start); + } + + if (count($types) > 0) { + $query->transactionTypes($types); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('t.account_id', $accountIds); + } + if ($categories->count() > 0) { + $categoryIds = $categories->pluck('id')->toArray(); + $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('category_transaction_journal.category_id', $categoryIds); + } + + // that should do it: + $first = $query->get(TransactionJournal::queryFields()); + + // then collection transactions (harder) + $query = $this->user->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')); + if (count($types) > 0) { + $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); + $query->whereIn('transaction_types.type', $types); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('transactions.account_id', $accountIds); + } + if ($categories->count() > 0) { + $categoryIds = $categories->pluck('id')->toArray(); + $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); + $query->whereIn('category_transaction.category_id', $categoryIds); + } + $second = $query->get('transaction_journals.*'); + $complete = $complete->merge($first); + $complete = $complete->merge($second); + + return $complete; + } + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : Collection + { + /** @var Collection $set */ + $query = $this->user + ->transactionjournals() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('category_transaction_journal.id') + ->before($end) + ->after($start); + + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('t.account_id', $accountIds); + } + + $set = $query->get(['transaction_journals.*']); + + if ($set->count() == 0) { + return new Collection; + } + + // grab all the transactions from this set. + // take only the journals with transactions that all have no category. + // select transactions left join journals where id in this set + // and left join transaction-category where null category + $journalIds = $set->pluck('id')->toArray(); + $secondQuery = $this->user->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transaction.transaction_journal_id') + ->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id') + ->whereNull('category_transaction.id') + ->whereIn('transaction_journals.id', $journalIds); + + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $secondQuery->whereIn('transactions.account_id', $accountIds); + } + + // this second set REALLY doesn't have any categories. + $secondSet = $secondQuery->get(['transactions.transaction_journal_id']); + $allIds = $secondSet->pluck('transaction_journal_id')->toArray(); + $return = $this->user->transactionjournals()->expanded()->whereIn('transaction_journals.id', $allIds)->get(TransactionJournal::queryFields()); + + return $return; + + + } + + /** + * @param Category $category + * @param Collection $accounts + * + * @return Carbon + */ + public function lastUseDate(Category $category, Collection $accounts): Carbon + { + $last = null; + + /** @var TransactionJournal $first */ + $lastJournalQuery = $category->transactionjournals()->orderBy('date', 'ASC'); + + if ($accounts->count() > 0) { + // filter journals: + $ids = $accounts->pluck('id')->toArray(); + $lastJournalQuery->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); + $lastJournalQuery->whereIn('t.account_id', $ids); + } + + $lastJournal = $lastJournalQuery->first(['transaction_journals.*']); + + if ($lastJournal) { + $last = $lastJournal->date; + } + + // check transactions: + + $lastTransactionQuery = $category->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'ASC'); + if ($accounts->count() > 0) { + // filter journals: + $ids = $accounts->pluck('id')->toArray(); + $lastTransactionQuery->whereIn('transactions.account_id', $ids); + } + + $lastTransaction = $lastJournalQuery->first(['transaction_journals.*']); + + if (!is_null($lastTransaction) && !is_null($last) && $lastTransaction->date < $last) { + $last = $lastTransaction->date; + } + + return $last; + } + + /** * @param Collection $categories * @param Collection $accounts * @param Carbon $start * @param Carbon $end * - * @return Collection - */ - public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection - { - - $set = $this->user->categories() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'category_transaction_journal.transaction_journal_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereIn('transaction_types.type', [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL]) - ->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()) - ->whereIn('categories.id', $categories->pluck('id')->toArray()) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') - ->groupBy('transaction_types.type') - ->groupBy('dateFormatted') - ->get( - [ - 'categories.*', - DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y") as `dateFormatted`'), - 'transaction_types.type', - DB::raw('SUM(`amount`) as `sum`'), - ] - ); - - return $set; - - } - - /** - * Returns a list of transaction journals in the range (all types, all accounts) that have no category - * associated to them. - * - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function listNoCategory(Carbon $start, Carbon $end): Collection - { - return $this->user - ->transactionjournals() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('category_transaction_journal.id') - ->before($end) - ->after($start) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->get(['transaction_journals.*']); - } - - /** - * Returns a collection of Categories appended with the amount of money that has been spent - * in these categories, based on the $accounts involved, in period X, grouped per month. - * The amount spent in category X in period X is saved in field "spent". - * - * @param $accounts - * @param $start - * @param $end - * - * @return Collection - */ - public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection - { - $accountIds = $accounts->pluck('id')->toArray(); - $query = $this->user->categories() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions AS t_src', function (JoinClause $join) { - $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_dest', function (JoinClause $join) { - $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); - } - ) - ->whereIn( - 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] - )// spent on these things. - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') - ->groupBy('dateFormatted'); - - if (count($accountIds) > 0) { - $query->whereIn('t_src.account_id', $accountIds)// from these accounts (spent) - ->whereNotIn('t_dest.account_id', $accountIds);//-- but not from these accounts (spent internally) - } - - $collection = $query->get( - [ - 'categories.*', - DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), - DB::raw('SUM(`t_src`.`amount`) AS `spent`'), - ] - ); - - return $collection; - } - - /** - * Returns the total amount of money related to transactions without any category connected to - * it. Returns either the earned amount. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * * @return string */ - public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string + public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string { - return $this->sumNoCategory($accounts, $start, $end, self::EARNED); - } - - /** - * Returns the total amount of money related to transactions without any category connected to - * it. Returns either the spent amount. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string - { - $sum = $this->sumNoCategory($accounts, $start, $end, self::SPENT); - if (is_null($sum)) { - return '0'; + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); + $sum = '0'; + foreach ($journals as $journal) { + $sum = bcadd(TransactionJournal::amount($journal), $sum); } return $sum; } /** - * Returns the total amount of money related to transactions without any category connected to - * it. Returns either the earned or the spent amount. + * @param array $data * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * @param int $group - * - * @return string + * @return Category */ - protected function sumNoCategory(Collection $accounts, Carbon $start, Carbon $end, $group = self::EARNED) + public function store(array $data): Category { - $accountIds = $accounts->pluck('id')->toArray(); - if ($group == self::EARNED) { - $types = [TransactionType::DEPOSIT]; - } else { - $types = [TransactionType::WITHDRAWAL]; - } - - // is withdrawal or transfer AND account_from is in the list of $accounts - $query = $this->user - ->transactionjournals() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('category_transaction_journal.id') - ->before($end) - ->after($start) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->having('transaction_count', '=', 1) - ->transactionTypes($types); - - if (count($accountIds) > 0) { - $query->whereIn('transactions.account_id', $accountIds); - } - - - $single = $query->first( + $newCategory = Category::firstOrCreateEncrypted( [ - DB::raw('SUM(`transactions`.`amount`) as `sum`'), - DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), + 'user_id' => $data['user'], + 'name' => $data['name'], ] ); - if (!is_null($single)) { - return $single->sum; - } + $newCategory->save(); - return '0'; + return $newCategory; + } + /** + * @param Category $category + * @param array $data + * + * @return Category + */ + public function update(Category $category, array $data): Category + { + // update the account: + $category->name = $data['name']; + $category->save(); + + return $category; } } diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 5c5966c8b7..e49dd8cb85 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -4,6 +4,8 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Category; use Carbon\Carbon; +use FireflyIII\Models\Category; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; /** @@ -15,18 +17,149 @@ interface CategoryRepositoryInterface { + // /** + // * Returns a collection of Categories appended with the amount of money that has been earned + // * in these categories, based on the $accounts involved, in period X, grouped per month. + // * The amount earned in category X in period X is saved in field "earned". + // * + // * @param $accounts + // * @param $start + // * @param $end + // * + // * @return Collection + // */ + // public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection; + /** - * Returns a collection of Categories appended with the amount of money that has been earned - * in these categories, based on the $accounts involved, in period X, grouped per month. - * The amount earned in category X in period X is saved in field "earned". + * @param Category $category * - * @param $accounts - * @param $start - * @param $end - * - * @return Collection + * @return bool */ - public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection; + public function destroy(Category $category): bool; + + // /** + // * This method returns a very special collection for each category: + // * + // * category, year, expense/earned, amount + // * + // * categories can be duplicated. + // * + // * @param Collection $categories + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection; + + // /** + // * Returns a list of transaction journals in the range (all types, all accounts) that have no category + // * associated to them. + // * + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function listNoCategory(Carbon $start, Carbon $end): Collection; + + // /** + // * Returns a collection of Categories appended with the amount of money that has been spent + // * in these categories, based on the $accounts involved, in period X, grouped per month. + // * The amount earned in category X in period X is saved in field "spent". + // * + // * @param $accounts + // * @param $start + // * @param $end + // * + // * @return Collection + // */ + // public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection; + + // /** + // * Returns the total amount of money related to transactions without any category connected to + // * it. Returns either the earned amount. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return string + // */ + // public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string; + + // /** + // * Returns the total amount of money related to transactions without any category connected to + // * it. Returns either the spent amount. + // * + // * @param Collection $accounts + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return string + // */ + // public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string; + + + // /** + // * @param Category $category + // * @param Carbon|null $start + // * @param Carbon|null $end + // * + // * @return int + // */ + // public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int; + + /** + * @param Collection $categories + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string; + + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category + // * from all the users accounts. + // * + // * @param Category $category + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return array + // */ + // public function earnedPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array; + + /** + * Find a category + * + * @param int $categoryId + * + * @return Category + */ + public function find(int $categoryId) : Category; + + // /** + // * @param Category $category + // * + // * @return Carbon + // */ + + /** + * @param Category $category + * @param Collection $accounts + * + * @return Carbon + */ + public function firstUseDate(Category $category, Collection $accounts): Carbon; /** * Returns a list of all the categories belonging to a user. @@ -36,67 +169,119 @@ interface CategoryRepositoryInterface public function getCategories(): Collection; /** - * This method returns a very special collection for each category: + * @param Category $category + * @param int $page + * @param int $pageSize * - * category, year, expense/earned, amount + * @return LengthAwarePaginator + */ + public function getJournals(Category $category, int $page, int $pageSize): LengthAwarePaginator; + + /** + * @param Collection $categories + * @param Collection $accounts + * @param array $types + * @param Carbon $start + * @param Carbon $end * - * categories can be duplicated. + * @return Collection + */ + public function journalsInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): Collection; + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end * + * @return Collection + */ + public function journalsInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : Collection; + + /** + * @param Category $category + * @param Collection $accounts + * + * @return Carbon + */ + public function lastUseDate(Category $category, Collection $accounts): Carbon; + + /** * @param Collection $categories * @param Collection $accounts * @param Carbon $start * @param Carbon $end * - * @return Collection - */ - public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection; - - /** - * Returns a list of transaction journals in the range (all types, all accounts) that have no category - * associated to them. - * - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function listNoCategory(Carbon $start, Carbon $end): Collection; - - /** - * Returns a collection of Categories appended with the amount of money that has been spent - * in these categories, based on the $accounts involved, in period X, grouped per month. - * The amount earned in category X in period X is saved in field "spent". - * - * @param $accounts - * @param $start - * @param $end - * - * @return Collection - */ - public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection; - - /** - * Returns the total amount of money related to transactions without any category connected to - * it. Returns either the earned amount. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * * @return string */ - public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string; + public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string; + // /** + // * @param Category $category + // * @param int $page + // * @param int $pageSize + // * + // * @return Collection + // */ + // public function getJournals(Category $category, int $page, int $pageSize = 50): Collection; + + // /** + // * @param Category $category + // * @param Collection $accounts + // * + // * @param Carbon $start + // * @param Carbon $end + // * + // * @return Collection + // */ + // public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection; + + // /** + // * @param Category $category + // * @param Carbon $start + // * @param Carbon $end + // * @param int $page + // * @param int $pageSize + // * + // * + // * @return Collection + // */ + // public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection; + + // /** + // * @param Category $category + // * + // * @return Carbon + // */ + // public function getLatestActivity(Category $category): Carbon; + + // /** + // * Returns an array with the following key:value pairs: + // * + // * yyyy-mm-dd: + // * + // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $category + // * from all the users accounts. + // * + // * @param Category $category + // * @param Carbon $start + // * @param Carbon $end + // * @param Collection $accounts + // * + // * @return array + // */ + // public function spentPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array; /** - * Returns the total amount of money related to transactions without any category connected to - * it. Returns either the spent amount. + * @param array $data * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return string + * @return Category */ - public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string; + public function store(array $data): Category; + /** + * @param Category $category + * @param array $data + * + * @return Category + */ + public function update(Category $category, array $data): Category; } diff --git a/app/Repositories/Category/SingleCategoryRepository.php b/app/Repositories/Category/SingleCategoryRepository.php deleted file mode 100644 index 7918ceac72..0000000000 --- a/app/Repositories/Category/SingleCategoryRepository.php +++ /dev/null @@ -1,322 +0,0 @@ -user = $user; - } - - /** - * @param Category $category - * @param Carbon|null $start - * @param Carbon|null $end - * - * @return int - */ - public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int - { - $query = $category->transactionjournals(); - if (!is_null($start)) { - $query->after($start); - } - if (!is_null($end)) { - $query->before($end); - } - - return $query->count(); - - } - - /** - * @param Category $category - * - * @return bool - */ - public function destroy(Category $category): bool - { - $category->delete(); - - return true; - } - - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category - * from all the users $accounts. - * - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - public function earnedPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array - { - /** @var Collection $query */ - $query = $category->transactionjournals() - ->expanded() - ->transactionTypes([TransactionType::DEPOSIT]) - ->before($end) - ->after($start) - ->groupBy('transaction_journals.date'); - - $query->leftJoin( - 'transactions as destination', function (JoinClause $join) { - $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); - } - ); - - - if ($accounts->count() > 0) { - $ids = $accounts->pluck('id')->toArray(); - $query->whereIn('destination.account.id', $ids); - } - - $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`destination`.`amount`) AS `sum`')]); - - $return = []; - foreach ($result->toArray() as $entry) { - $return[$entry['dateFormatted']] = $entry['sum']; - } - - return $return; - } - - /** - * Find a category - * - * @param int $categoryId - * - * @return Category - */ - public function find(int $categoryId) : Category - { - $category = $this->user->categories()->find($categoryId); - if (is_null($category)) { - $category = new Category; - } - - return $category; - } - - /** - * @param Category $category - * - * @return Carbon - */ - public function getFirstActivityDate(Category $category): Carbon - { - /** @var TransactionJournal $first */ - $first = $category->transactionjournals()->orderBy('date', 'ASC')->first(); - if ($first) { - return $first->date; - } - - return new Carbon; - - } - - /** - * @param Category $category - * @param int $page - * @param int $pageSize - * - * @return Collection - */ - public function getJournals(Category $category, int $page, int $pageSize = 50): Collection - { - $offset = $page > 0 ? $page * $pageSize : 0; - - return $category->transactionjournals()->expanded()->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); - - } - - /** - * @param Category $category - * @param Collection $accounts - * - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); - - return $category->transactionjournals() - ->after($start) - ->before($end) - ->expanded() - ->whereIn('source_account.id', $ids) - ->whereNotIn('destination_account.id', $ids) - ->get(TransactionJournal::queryFields()); - } - - /** - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param int $page - * @param int $pageSize - * - * - * @return Collection - */ - public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection - { - $offset = $page > 0 ? $page * $pageSize : 0; - - return $category->transactionjournals() - ->after($start) - ->before($end) - ->expanded() - ->take($pageSize) - ->offset($offset) - ->get(TransactionJournal::queryFields()); - } - - /** - * @param Category $category - * - * @return Carbon - */ - public function getLatestActivity(Category $category): Carbon - { - $first = new Carbon('1900-01-01'); - $second = new Carbon('1900-01-01'); - $latest = $category->transactionjournals() - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->first(); - if ($latest) { - $first = $latest->date; - } - - // could also be a transaction, nowadays: - $latestTransaction = $category->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->first(['transactions.*', 'transaction_journals.date']); - if ($latestTransaction) { - $second = new Carbon($latestTransaction->date); - } - if ($first > $second) { - return $first; - } - - return $second; - } - - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $category - * from all the users accounts. - * - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - public function spentPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array - { - /** @var Collection $query */ - $query = $category->transactionjournals() - ->expanded() - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->before($end) - ->after($start) - ->groupBy('transaction_journals.date'); - $query->leftJoin( - 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); - } - ); - - if ($accounts->count() > 0) { - $ids = $accounts->pluck('id')->toArray(); - $query->whereIn('source.account_id', $ids); - } - - $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`source`.`amount`) AS `sum`')]); - - $return = []; - foreach ($result->toArray() as $entry) { - $return[$entry['dateFormatted']] = $entry['sum']; - } - - return $return; - } - - /** - * @param array $data - * - * @return Category - */ - public function store(array $data): Category - { - $newCategory = Category::firstOrCreateEncrypted( - [ - 'user_id' => $data['user'], - 'name' => $data['name'], - ] - ); - $newCategory->save(); - - return $newCategory; - } - - /** - * @param Category $category - * @param array $data - * - * @return Category - */ - public function update(Category $category, array $data): Category - { - // update the account: - $category->name = $data['name']; - $category->save(); - - return $category; - } -} diff --git a/app/Repositories/Category/SingleCategoryRepositoryInterface.php b/app/Repositories/Category/SingleCategoryRepositoryInterface.php deleted file mode 100644 index 0aa59aa5e0..0000000000 --- a/app/Repositories/Category/SingleCategoryRepositoryInterface.php +++ /dev/null @@ -1,138 +0,0 @@ - - * - * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category - * from all the users accounts. - * - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - public function earnedPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array; - - /** - * Find a category - * - * @param int $categoryId - * - * @return Category - */ - public function find(int $categoryId) : Category; - - /** - * @param Category $category - * - * @return Carbon - */ - public function getFirstActivityDate(Category $category): Carbon; - - /** - * @param Category $category - * @param int $page - * @param int $pageSize - * - * @return Collection - */ - public function getJournals(Category $category, int $page, int $pageSize = 50): Collection; - - /** - * @param Category $category - * @param Collection $accounts - * - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection; - - /** - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param int $page - * @param int $pageSize - * - * - * @return Collection - */ - public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection; - - /** - * @param Category $category - * - * @return Carbon - */ - public function getLatestActivity(Category $category): Carbon; - - /** - * Returns an array with the following key:value pairs: - * - * yyyy-mm-dd: - * - * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $category - * from all the users accounts. - * - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return array - */ - public function spentPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array; - - - /** - * @param array $data - * - * @return Category - */ - public function store(array $data): Category; - - /** - * @param Category $category - * @param array $data - * - * @return Category - */ - public function update(Category $category, array $data): Category; -} From a90d09560986897686b93a520b1bb3cbffa33e92 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 9 May 2016 18:06:53 +0200 Subject: [PATCH 092/206] More stuff for categories. --- app/Http/Controllers/CategoryController.php | 2 +- .../Category/CategoryRepository.php | 21 +++++++++++-------- .../Category/CategoryRepositoryInterface.php | 2 ++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index dc23f9614d..ee0aac585e 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -163,7 +163,7 @@ class CategoryController extends Controller // oldest transaction in category: //$start = $repository->getFirstActivityDate($category); - $start = $repository->firstUseDate($category, new Account); + $start = $repository->firstUseDate($category, new Collection); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($start, $range); $end = Navigation::endOfX(new Carbon, $range); diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 2bfb97930d..613f990aca 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -573,7 +573,7 @@ class CategoryRepository implements CategoryRepositoryInterface $first = null; /** @var TransactionJournal $first */ - $firstJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC'); + $firstJournalQuery = $category->transactionjournals()->orderBy('date', 'ASC'); if ($accounts->count() > 0) { // filter journals: @@ -643,17 +643,21 @@ class CategoryRepository implements CategoryRepositoryInterface $first = $query->get(TransactionJournal::queryFields()); // then collection transactions (harder) - $query = $this->user->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id'); + $query = $this->user->transactions(); $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); $query->where('category_transaction.category_id', $category->id); - $second = $query->get('transaction_journals.*'); + $second = $query->get(['transaction_journals.*']); $complete = $complete->merge($first); $complete = $complete->merge($second); - return $complete; + // create paginator + $offset = ($page - 1) * $pageSize; + $subSet = $complete->slice($offset, $pageSize); + $paginator = new LengthAwarePaginator($subSet, $complete->count(), $pageSize, $page); + + return $paginator; } /** @@ -696,7 +700,6 @@ class CategoryRepository implements CategoryRepositoryInterface // then collection transactions (harder) $query = $this->user->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id') ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')); if (count($types) > 0) { @@ -712,7 +715,7 @@ class CategoryRepository implements CategoryRepositoryInterface $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); $query->whereIn('category_transaction.category_id', $categoryIds); } - $second = $query->get('transaction_journals.*'); + $second = $query->get(['transaction_journals.*']); $complete = $complete->merge($first); $complete = $complete->merge($second); @@ -785,7 +788,7 @@ class CategoryRepository implements CategoryRepositoryInterface $last = null; /** @var TransactionJournal $first */ - $lastJournalQuery = $category->transactionjournals()->orderBy('date', 'ASC'); + $lastJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC'); if ($accounts->count() > 0) { // filter journals: @@ -804,7 +807,7 @@ class CategoryRepository implements CategoryRepositoryInterface $lastTransactionQuery = $category->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'ASC'); + ->orderBy('transaction_journals.date', 'DESC'); if ($accounts->count() > 0) { // filter journals: $ids = $accounts->pluck('id')->toArray(); diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index e49dd8cb85..9cf56f4139 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -198,6 +198,8 @@ interface CategoryRepositoryInterface public function journalsInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : Collection; /** + * Return most recent transaction(journal) date. + * * @param Category $category * @param Collection $accounts * From cda6cfb4cdbda41a188c5bd4cf137a512dec9376 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 9 May 2016 20:15:26 +0200 Subject: [PATCH 093/206] Fix more charts. --- .../Controllers/Chart/CategoryController.php | 518 +++++++++--------- app/Models/Category.php | 2 +- .../Category/CategoryRepository.php | 31 +- .../Category/CategoryRepositoryInterface.php | 9 + 4 files changed, 303 insertions(+), 257 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index b7c974efab..cd1a09c2ea 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -8,10 +8,10 @@ use Carbon\Carbon; use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; -use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; +use Log; use Navigation; use Preferences; use Response; @@ -45,37 +45,34 @@ class CategoryController extends Controller /** * Show an overview for a category for all time, per month/week/year. * - * @param CRI $repository + * @param CRI $repository * @param Category $category * * @return \Symfony\Component\HttpFoundation\Response */ public function all(CRI $repository, Category $category) { - /** - // oldest transaction in category: - $start = $repository->getFirstActivityDate($category); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($start, $range); - $end = new Carbon; - $entries = new Collection; - // chart properties for cache: - $cache = new CacheProperties(); + $start = $repository->firstUseDate($category, new Collection); + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $categoryCollection = new Collection([$category]); + $end = new Carbon; + $entries = new Collection; + $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('all'); $cache->addProperty('categories'); if ($cache->has()) { - return Response::json($cache->get()); + //return Response::json($cache->get()); } - $spentArray = $repository->spentPerDay($category, $start, $end, new Collection); - $earnedArray = $repository->earnedPerDay($category, $start, $end, new Collection); while ($start <= $end) { $currentEnd = Navigation::endOfPeriod($start, $range); - $spent = $this->getSumOfRange($start, $currentEnd, $spentArray); - $earned = $this->getSumOfRange($start, $currentEnd, $earnedArray); - $date = Navigation::periodShow($start, $range); + Log::debug('Searching for expenses from ' . $start . ' to ' . $currentEnd); + $spent = $repository->spentInPeriod($categoryCollection, new Collection, $start, $currentEnd); + $earned = $repository->earnedInPeriod($categoryCollection, new Collection, $start, $currentEnd); + $date = Navigation::periodShow($start, $range); $entries->push([clone $start, $date, $spent, $earned]); $start = Navigation::addPeriod($start, $range, 0); } @@ -85,25 +82,25 @@ class CategoryController extends Controller $data = $this->generator->all($entries); $cache->store($data); + return ' ' . json_encode($data); + return Response::json($data); - * **/ + } /** - * @param CRI $repository + * @param CRI $repository * @param Category $category * * @return \Symfony\Component\HttpFoundation\Response */ public function currentPeriod(CRI $repository, Category $category) { - /** $start = clone session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); $data = $this->makePeriodChart($repository, $category, $start, $end); return Response::json($data); - * **/ } /** @@ -121,29 +118,29 @@ class CategoryController extends Controller public function earnedInPeriod(CRI $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { /** - $cache = new CacheProperties; // chart properties for cache: - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - $cache->addProperty('category'); - $cache->addProperty('earned-in-period'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - $set = $repository->earnedForAccountsPerMonth($accounts, $start, $end); - $categories = $set->unique('id')->sortBy( - function (Category $category) { - return $category->name; - } - ); - $entries = $this->filterCollection($start, $end, $set, $categories); - $data = $this->generator->earnedInPeriod($categories, $entries); - $cache->store($data); - - return $data; - **/ + * $cache = new CacheProperties; // chart properties for cache: + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($reportType); + * $cache->addProperty($accounts); + * $cache->addProperty('category'); + * $cache->addProperty('earned-in-period'); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * $set = $repository->earnedForAccountsPerMonth($accounts, $start, $end); + * $categories = $set->unique('id')->sortBy( + * function (Category $category) { + * return $category->name; + * } + * ); + * $entries = $this->filterCollection($start, $end, $set, $categories); + * $data = $this->generator->earnedInPeriod($categories, $entries); + * $cache->store($data); + * + * return $data; + **/ } /** @@ -151,16 +148,12 @@ class CategoryController extends Controller * * @param CRI $repository * - * @param ARI $accountRepository - * * @return \Symfony\Component\HttpFoundation\Response */ - public function frontpage(CRI $repository, ARI $accountRepository) + public function frontpage(CRI $repository) { - /** $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); - // chart properties for cache: $cache = new CacheProperties; $cache->addProperty($start); @@ -168,18 +161,23 @@ class CategoryController extends Controller $cache->addProperty('category'); $cache->addProperty('frontpage'); if ($cache->has()) { - return Response::json($cache->get()); + //return Response::json($cache->get()); + } + $categories = $repository->getCategories(); + $set = new Collection; + /** @var Category $category */ + foreach ($categories as $category) { + $spent = $repository->spentInPeriod(new Collection([$category]), new Collection, $start, $end); + Log::debug('Spent for ' . $category->name . ' is ' . $spent . ' (' . bccomp($spent, '0') . ')'); + if (bccomp($spent, '0') === -1) { + $category->spent = $spent; + $set->push($category); + } } - - // get data for categories (and "no category"): - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); - $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); - $outside = $repository->sumSpentNoCategory($accounts, $start, $end); - // this is a "fake" entry for the "no category" entry. - $entry = new stdClass(); + $entry = new stdClass; $entry->name = trans('firefly.no_category'); - $entry->spent = $outside; + $entry->spent = $repository->spentInPeriodWithoutCategory(new Collection, $start, $end); $set->push($entry); $set = $set->sortBy('spent'); @@ -187,7 +185,7 @@ class CategoryController extends Controller $cache->store($data); return Response::json($data); - **/ + } /** @@ -202,76 +200,76 @@ class CategoryController extends Controller public function multiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) { /** - // /** @var CRI $repository - // $repository = app(CRI::class); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($reportType); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($accounts); - $cache->addProperty($categories); - $cache->addProperty('multiYearCategory'); - - if ($cache->has()) { - return Response::json($cache->get()); - } - - $entries = new Collection; - $set = $repository->listMultiYear($categories, $accounts, $start, $end); - - /** @var Category $category - foreach ($categories as $category) { - $entry = ['name' => '', 'spent' => [], 'earned' => []]; - - $currentStart = clone $start; - while ($currentStart < $end) { - // fix the date: - $year = $currentStart->year; - $currentEnd = clone $currentStart; - $currentEnd->endOfYear(); - - - // get data: - if (is_null($category->id)) { - $name = trans('firefly.noCategory'); - $spent = $repository->sumSpentNoCategory($accounts, $currentStart, $currentEnd); - $earned = $repository->sumEarnedNoCategory($accounts, $currentStart, $currentEnd); - } else { - // get from set: - $entrySpent = $set->filter( - function (Category $cat) use ($year, $category) { - return ($cat->type == 'Withdrawal' && $cat->dateFormatted == $year && $cat->id == $category->id); - } - )->first(); - $entryEarned = $set->filter( - function (Category $cat) use ($year, $category) { - return ($cat->type == 'Deposit' && $cat->dateFormatted == $year && $cat->id == $category->id); - } - )->first(); - - $name = $category->name; - $spent = !is_null($entrySpent) ? $entrySpent->sum : 0; - $earned = !is_null($entryEarned) ? $entryEarned->sum : 0; - } - - // save to array: - $entry['name'] = $name; - $entry['spent'][$year] = ($spent * -1); - $entry['earned'][$year] = $earned; - - // jump to next year. - $currentStart = clone $currentEnd; - $currentStart->addDay(); - } - $entries->push($entry); - } - // generate chart with data: - $data = $this->generator->multiYear($entries); - $cache->store($data); - - return Response::json($data); + * // /** @var CRI $repository + * // $repository = app(CRI::class); + * + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($reportType); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($accounts); + * $cache->addProperty($categories); + * $cache->addProperty('multiYearCategory'); + * + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * $entries = new Collection; + * $set = $repository->listMultiYear($categories, $accounts, $start, $end); + * + * /** @var Category $category + * foreach ($categories as $category) { + * $entry = ['name' => '', 'spent' => [], 'earned' => []]; + * + * $currentStart = clone $start; + * while ($currentStart < $end) { + * // fix the date: + * $year = $currentStart->year; + * $currentEnd = clone $currentStart; + * $currentEnd->endOfYear(); + * + * + * // get data: + * if (is_null($category->id)) { + * $name = trans('firefly.noCategory'); + * $spent = $repository->sumSpentNoCategory($accounts, $currentStart, $currentEnd); + * $earned = $repository->sumEarnedNoCategory($accounts, $currentStart, $currentEnd); + * } else { + * // get from set: + * $entrySpent = $set->filter( + * function (Category $cat) use ($year, $category) { + * return ($cat->type == 'Withdrawal' && $cat->dateFormatted == $year && $cat->id == $category->id); + * } + * )->first(); + * $entryEarned = $set->filter( + * function (Category $cat) use ($year, $category) { + * return ($cat->type == 'Deposit' && $cat->dateFormatted == $year && $cat->id == $category->id); + * } + * )->first(); + * + * $name = $category->name; + * $spent = !is_null($entrySpent) ? $entrySpent->sum : 0; + * $earned = !is_null($entryEarned) ? $entryEarned->sum : 0; + * } + * + * // save to array: + * $entry['name'] = $name; + * $entry['spent'][$year] = ($spent * -1); + * $entry['earned'][$year] = $earned; + * + * // jump to next year. + * $currentStart = clone $currentEnd; + * $currentStart->addDay(); + * } + * $entries->push($entry); + * } + * // generate chart with data: + * $data = $this->generator->multiYear($entries); + * $cache->store($data); + * + * return Response::json($data); * */ @@ -289,54 +287,54 @@ class CategoryController extends Controller public function period(Category $category, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { /** - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - $cache->addProperty($category->id); - $cache->addProperty('category'); - $cache->addProperty('period'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - /** @var CategoryRepositoryInterface $repository - $repository = app(CategoryRepositoryInterface::class); - // loop over period, add by users range: - $current = clone $start; - $viewRange = Preferences::get('viewRange', '1M')->data; - $format = strval(trans('config.month')); - $set = new Collection; - while ($current < $end) { - $currentStart = clone $current; - $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); - - $spent = strval(array_sum($repository->spentPerDay($category, $currentStart, $currentEnd, $accounts))); - $earned = strval(array_sum($repository->earnedPerDay($category, $currentStart, $currentEnd, $accounts))); - - $entry = [ - $category->name, - $currentStart->formatLocalized($format), - $spent, - $earned, - - ]; - $set->push($entry); - $currentEnd->addDay(); - $current = clone $currentEnd; - } - $data = $this->generator->period($set); - $cache->store($data); - - return Response::json($data); + * // chart properties for cache: + * $cache = new CacheProperties(); + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($reportType); + * $cache->addProperty($accounts); + * $cache->addProperty($category->id); + * $cache->addProperty('category'); + * $cache->addProperty('period'); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * /** @var CategoryRepositoryInterface $repository + * $repository = app(CategoryRepositoryInterface::class); + * // loop over period, add by users range: + * $current = clone $start; + * $viewRange = Preferences::get('viewRange', '1M')->data; + * $format = strval(trans('config.month')); + * $set = new Collection; + * while ($current < $end) { + * $currentStart = clone $current; + * $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); + * + * $spent = strval(array_sum($repository->spentPerDay($category, $currentStart, $currentEnd, $accounts))); + * $earned = strval(array_sum($repository->earnedPerDay($category, $currentStart, $currentEnd, $accounts))); + * + * $entry = [ + * $category->name, + * $currentStart->formatLocalized($format), + * $spent, + * $earned, + * + * ]; + * $set->push($entry); + * $currentEnd->addDay(); + * $current = clone $currentEnd; + * } + * $data = $this->generator->period($set); + * $cache->store($data); + * + * return Response::json($data); * **/ } /** - * @param CRI $repository + * @param CRI $repository * @param Category $category * * @param $date @@ -345,7 +343,6 @@ class CategoryController extends Controller */ public function specificPeriod(CRI $repository, Category $category, $date) { - /** $carbon = new Carbon($date); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($carbon, $range); @@ -353,8 +350,6 @@ class CategoryController extends Controller $data = $this->makePeriodChart($repository, $category, $start, $end); return Response::json($data); - - **/ } /** @@ -373,30 +368,30 @@ class CategoryController extends Controller public function spentInPeriod(CRI $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts) { /** - $cache = new CacheProperties; // chart properties for cache: - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - $cache->addProperty('category'); - $cache->addProperty('spent-in-period'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - - $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); - $categories = $set->unique('id')->sortBy( - function (Category $category) { - return $category->name; - } - ); - $entries = $this->filterCollection($start, $end, $set, $categories); - $entries = $this->invertSelection($entries); - $data = $this->generator->spentInPeriod($categories, $entries); - $cache->store($data); - - return $data; + * $cache = new CacheProperties; // chart properties for cache: + * $cache->addProperty($start); + * $cache->addProperty($end); + * $cache->addProperty($reportType); + * $cache->addProperty($accounts); + * $cache->addProperty('category'); + * $cache->addProperty('spent-in-period'); + * if ($cache->has()) { + * return Response::json($cache->get()); + * } + * + * + * $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); + * $categories = $set->unique('id')->sortBy( + * function (Category $category) { + * return $category->name; + * } + * ); + * $entries = $this->filterCollection($start, $end, $set, $categories); + * $entries = $this->invertSelection($entries); + * $data = $this->generator->spentInPeriod($categories, $entries); + * $cache->store($data); + * + * return $data; * */ } @@ -411,33 +406,33 @@ class CategoryController extends Controller private function filterCollection(Carbon $start, Carbon $end, Collection $set, Collection $categories): Collection { /** - $entries = new Collection; - - while ($start < $end) { // filter the set: - $row = [clone $start]; - $currentSet = $set->filter( // get possibly relevant entries from the big $set - function (Category $category) use ($start) { - return $category->dateFormatted == $start->format('Y-m'); - } - ); - /** @var Category $category - foreach ($categories as $category) { // check for each category if its in the current set. - $entry = $currentSet->filter( // if its in there, use the value. - function (Category $cat) use ($category) { - return ($cat->id == $category->id); - } - )->first(); - if (!is_null($entry)) { - $row[] = $entry->earned ? round($entry->earned, 2) : round($entry->spent, 2); - } else { - $row[] = 0; - } - } - $entries->push($row); - $start->addMonth(); - } - - return $entries; + * $entries = new Collection; + * + * while ($start < $end) { // filter the set: + * $row = [clone $start]; + * $currentSet = $set->filter( // get possibly relevant entries from the big $set + * function (Category $category) use ($start) { + * return $category->dateFormatted == $start->format('Y-m'); + * } + * ); + * /** @var Category $category + * foreach ($categories as $category) { // check for each category if its in the current set. + * $entry = $currentSet->filter( // if its in there, use the value. + * function (Category $cat) use ($category) { + * return ($cat->id == $category->id); + * } + * )->first(); + * if (!is_null($entry)) { + * $row[] = $entry->earned ? round($entry->earned, 2) : round($entry->spent, 2); + * } else { + * $row[] = 0; + * } + * } + * $entries->push($row); + * $start->addMonth(); + * } + * + * return $entries; * */ } @@ -451,23 +446,23 @@ class CategoryController extends Controller private function invertSelection(Collection $entries): Collection { /** - $result = new Collection; - foreach ($entries as $entry) { - $new = [$entry[0]]; - $count = count($entry); - for ($i = 1; $i < $count; $i++) { - $new[$i] = ($entry[$i] * -1); - } - $result->push($new); - } - - return $result; + * $result = new Collection; + * foreach ($entries as $entry) { + * $new = [$entry[0]]; + * $count = count($entry); + * for ($i = 1; $i < $count; $i++) { + * $new[$i] = ($entry[$i] * -1); + * } + * $result->push($new); + * } + * + * return $result; * **/ } /** - * @param CRI $repository + * @param CRI $repository * @param Category $category * @param Carbon $start * @param Carbon $end @@ -476,28 +471,25 @@ class CategoryController extends Controller */ private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end) { - /** - // chart properties for cache: - $cache = new CacheProperties; + $categoryCollection = new Collection([$category]); + $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty($category->id); $cache->addProperty('specific-period'); + if ($cache->has()) { - return $cache->get(); // + // return $cache->get(); } $entries = new Collection; - - // get amount earned in period, grouped by day. - // get amount spent in period, grouped by day. - $spentArray = $repository->spentPerDay($category, $start, $end, new Collection); - $earnedArray = $repository->earnedPerDay($category, $start, $end, new Collection); - + Log::debug('Start is ' . $start . ' en end is ' . $end); while ($start <= $end) { - $str = $start->format('Y-m-d'); - $spent = $spentArray[$str] ?? '0'; - $earned = $earnedArray[$str] ?? '0'; - $date = Navigation::periodShow($start, '1D'); + Log::debug('Now at ' . $start); + $spent = $repository->spentInPeriod($categoryCollection, new Collection, $start, $start); + Log::debug('spent: ' . $spent); + $earned = $repository->earnedInPeriod($categoryCollection, new Collection, $start, $start); + Log::debug('earned: ' . $earned); + $date = Navigation::periodShow($start, '1D'); $entries->push([clone $start, $date, $spent, $earned]); $start->addDay(); } @@ -506,7 +498,27 @@ class CategoryController extends Controller $cache->store($data); return $data; - */ + + /** + * // get amount earned in period, grouped by day. + * // get amount spent in period, grouped by day. + * $spentArray = $repository->spentPerDay($category, $start, $end, new Collection); + * $earnedArray = $repository->earnedPerDay($category, $start, $end, new Collection); + * + * while ($start <= $end) { + * $str = $start->format('Y-m-d'); + * $spent = $spentArray[$str] ?? '0'; + * $earned = $earnedArray[$str] ?? '0'; + * $date = Navigation::periodShow($start, '1D'); + * $entries->push([clone $start, $date, $spent, $earned]); + * $start->addDay(); + * } + * + * $data = $this->generator->period($entries); + * $cache->store($data); + * + * return $data; + */ } } diff --git a/app/Models/Category.php b/app/Models/Category.php index 325ba87e39..cc7a881a47 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -20,7 +20,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property-read \Illuminate\Database\Eloquent\Collection|TransactionJournal[] $transactionjournals * @property-read \FireflyIII\User $user * @property string $dateFormatted - * @property float $spent + * @property string $spent * @property \Carbon\Carbon $lastActivity * @property string $type * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereId($value) diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 613f990aca..192dfbf5bf 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -378,7 +378,7 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string { - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; $journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); $sum = '0'; foreach ($journals as $journal) { @@ -652,6 +652,13 @@ class CategoryRepository implements CategoryRepositoryInterface $complete = $complete->merge($first); $complete = $complete->merge($second); + // sort: + $complete = $complete->sortByDesc( + function (TransactionJournal $journal) { + return $journal->date->format('Ymd'); + } + ); + // create paginator $offset = ($page - 1) * $pageSize; $subSet = $complete->slice($offset, $pageSize); @@ -677,7 +684,7 @@ class CategoryRepository implements CategoryRepositoryInterface // first collect actual transaction journals (fairly easy) $query = $this->user->transactionjournals()->expanded(); - if ($end > $start) { + if ($end >= $start) { $query->before($end)->after($start); } @@ -833,7 +840,7 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string { - $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; $journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); $sum = '0'; foreach ($journals as $journal) { @@ -843,6 +850,24 @@ class CategoryRepository implements CategoryRepositoryInterface return $sum; } + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string + { + $journals = $this->journalsInPeriodWithoutCategory($accounts, $start, $end); + $sum = '0'; + foreach ($journals as $journal) { + $sum = bcadd(TransactionJournal::amount($journal), $sum); + } + + return $sum; + } + /** * @param array $data * diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 9cf56f4139..b561dd2060 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -188,6 +188,15 @@ interface CategoryRepositoryInterface */ public function journalsInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): Collection; + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string; + /** * @param Collection $accounts * @param Carbon $start From 7c7740d3ba8606e1a1e71aca2b5e926159549d47 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 10 May 2016 09:33:23 +0200 Subject: [PATCH 094/206] Should fix frontpage. --- app/Models/TransactionJournal.php | 5 +++++ app/Repositories/Account/AccountRepository.php | 3 +++ 2 files changed, 8 insertions(+) diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 6a16338f95..5b0ec94ad5 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -338,6 +338,11 @@ class TransactionJournal extends TransactionJournalSupport $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); } ); + $query->orderBy('transaction_journals.date', 'DESC'); + $query->orderBy('transaction_journals.order', 'ASC'); + $query->orderBy('transaction_journals.id', 'DESC'); + + $query->groupBy('transaction_journals.id'); $query->with(['categories', 'budgets', 'attachments', 'bill','transactions']); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 198c9bd9b6..4353db8d10 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -246,6 +246,9 @@ class AccountRepository implements AccountRepositoryInterface } )->where('source.account_id', $account->id); + $query->take(10); + + $set = $query->get(TransactionJournal::queryFields()); return $set; From 934656c95489054705454ec069b7c7f868ef7174 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 07:57:16 +0200 Subject: [PATCH 095/206] Some small changes. --- app/Models/TransactionJournal.php | 10 +++++++++- app/Repositories/Account/AccountRepository.php | 9 +++++---- app/Repositories/Bill/BillRepository.php | 4 +--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 6a16338f95..131b558f2d 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -319,6 +319,14 @@ class TransactionJournal extends TransactionJournalSupport return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); } + public function scopeSortCorrectly(EloquentBuilder $query) + { + $query->orderBy('transaction_journals.date', 'DESC'); + $query->orderBy('transaction_journals.order', 'ASC'); + $query->orderBy('transaction_journals.id', 'DESC'); + + } + /** * @param EloquentBuilder $query */ @@ -339,7 +347,7 @@ class TransactionJournal extends TransactionJournalSupport } ); $query->groupBy('transaction_journals.id'); - $query->with(['categories', 'budgets', 'attachments', 'bill','transactions']); + $query->with(['categories', 'budgets', 'attachments', 'bill', 'transactions']); } /** diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 897d98ed41..34235f7177 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -236,8 +236,10 @@ class AccountRepository implements AccountRepositoryInterface $query = $this->user ->transactionjournals() ->expanded() + ->sortCorrectly() ->before($end) - ->after($start); + ->after($start) + ->take(10); // expand query: $query->leftJoin( @@ -450,7 +452,7 @@ class AccountRepository implements AccountRepositoryInterface $journal = TransactionJournal:: leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.account_id', $account->id) - ->orderBy('transaction_journals.date', 'ASC') + ->sortCorrectly() ->first(['transaction_journals.*']); if (is_null($journal)) { $date = new Carbon; @@ -495,11 +497,10 @@ class AccountRepository implements AccountRepositoryInterface public function openingBalanceTransaction(Account $account): TransactionJournal { $journal = TransactionJournal - ::orderBy('transaction_journals.date', 'ASC') + ::sortCorrectly() ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.account_id', $account->id) ->transactionTypes([TransactionType::OPENING_BALANCE]) - ->orderBy('created_at', 'ASC') ->first(['transaction_journals.*']); if (is_null($journal)) { return new TransactionJournal; diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index bf02a92306..be4dd3c63d 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -315,9 +315,7 @@ class BillRepository implements BillRepositoryInterface $offset = ($page - 1) * $pageSize; $query = $bill->transactionjournals() ->expanded() - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC'); + ->sortCorrectly(); $count = $query->count(); $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); From 9f8faf15f1be320f9a3c34200ecba5ecb371f9b3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 08:10:05 +0200 Subject: [PATCH 096/206] Reinstate sorting. --- app/Http/Controllers/Chart/CategoryController.php | 2 -- app/Http/Controllers/TagController.php | 2 +- app/Repositories/Account/AccountRepository.php | 6 +++++- app/Repositories/Budget/BudgetRepository.php | 3 +++ app/Repositories/Category/CategoryRepository.php | 6 +++--- app/Repositories/Journal/JournalRepository.php | 4 ++-- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index cd1a09c2ea..d9f8d518e2 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -82,8 +82,6 @@ class CategoryController extends Controller $data = $this->generator->all($entries); $cache->store($data); - return ' ' . json_encode($data); - return Response::json($data); } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index b873c84e4b..db5e6dbae5 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -221,7 +221,7 @@ class TagController extends Controller $subTitle = $tag->tag; $subTitleIcon = 'fa-tag'; /** @var Collection $journals */ - $journals = $tag->transactionjournals()->expanded()->get(TransactionJournal::queryFields()); + $journals = $tag->transactionjournals()->sortCorrectly()->expanded()->get(TransactionJournal::queryFields()); $sum = $journals->sum( function (TransactionJournal $journal) { diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 34235f7177..62212762c6 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -180,6 +180,7 @@ class AccountRepository implements AccountRepositoryInterface $ids = $accounts->pluck('id')->toArray(); $journals = $this->user->transactionjournals() ->expanded() + ->sortCorrectly() ->before($end) ->where('destination_account.id', $account->id) ->whereIn('source_account.id', $ids) @@ -239,7 +240,7 @@ class AccountRepository implements AccountRepositoryInterface ->sortCorrectly() ->before($end) ->after($start) - ->take(10); + ->take(10); // expand query: $query->leftJoin( @@ -269,6 +270,7 @@ class AccountRepository implements AccountRepositoryInterface $ids = $accounts->pluck('id')->toArray(); $journals = $this->user->transactionjournals() ->expanded() + ->sortCorrectly() ->before($end) ->where('source_account.id', $account->id) ->whereIn('destination_account.id', $ids) @@ -290,6 +292,7 @@ class AccountRepository implements AccountRepositoryInterface $offset = ($page - 1) * $pageSize; $query = $this->user ->transactionJournals() + ->sortCorrectly() ->expanded(); // expand query: @@ -321,6 +324,7 @@ class AccountRepository implements AccountRepositoryInterface $query = $this->user ->transactionJournals() ->expanded() + ->sortCorrectly() ->where( function (Builder $q) use ($account) { $q->where('destination_account.id', $account->id); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index baba663724..a5a135ab2b 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -876,6 +876,7 @@ class BudgetRepository implements BudgetRepositoryInterface $journalQuery = $this->user->transactionjournals() ->expanded() ->before($end) + ->sortCorrectly() ->after($start) ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->whereIn('budget_transaction_journal.budget_id', $budgets->pluck('id')->toArray()); @@ -892,6 +893,7 @@ class BudgetRepository implements BudgetRepositoryInterface $transactionQuery = $this->user->transactionjournals() ->expanded() ->before($end) + ->sortCorrectly() ->after($start) ->leftJoin('transactions as related', 'related.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'related.id') @@ -923,6 +925,7 @@ class BudgetRepository implements BudgetRepositoryInterface $set = $this->user ->transactionjournals() ->expanded() + ->sortCorrectly() ->transactionTypes([TransactionType::WITHDRAWAL]) ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->whereNull('budget_transaction_journal.id') diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 192dfbf5bf..f76c5fc3bb 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -637,7 +637,7 @@ class CategoryRepository implements CategoryRepositoryInterface { $complete = new Collection; // first collect actual transaction journals (fairly easy) - $query = $this->user->transactionjournals()->expanded(); + $query = $this->user->transactionjournals()->expanded()->sortCorrectly(); $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); $query->where('category_transaction_journal.category_id', $category->id); $first = $query->get(TransactionJournal::queryFields()); @@ -682,7 +682,7 @@ class CategoryRepository implements CategoryRepositoryInterface { $complete = new Collection; // first collect actual transaction journals (fairly easy) - $query = $this->user->transactionjournals()->expanded(); + $query = $this->user->transactionjournals()->expanded()->sortCorrectly(); if ($end >= $start) { $query->before($end)->after($start); @@ -777,7 +777,7 @@ class CategoryRepository implements CategoryRepositoryInterface // this second set REALLY doesn't have any categories. $secondSet = $secondQuery->get(['transactions.transaction_journal_id']); $allIds = $secondSet->pluck('transaction_journal_id')->toArray(); - $return = $this->user->transactionjournals()->expanded()->whereIn('transaction_journals.id', $allIds)->get(TransactionJournal::queryFields()); + $return = $this->user->transactionjournals()->sortCorrectly()->expanded()->whereIn('transaction_journals.id', $allIds)->get(TransactionJournal::queryFields()); return $return; diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index af03b870b3..7d05411f8c 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -128,7 +128,7 @@ class JournalRepository implements JournalRepositoryInterface public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator { $offset = ($page - 1) * $pageSize; - $query = $this->user->transactionJournals()->expanded(); + $query = $this->user->transactionJournals()->expanded()->sortCorrectly(); if (count($types) > 0) { $query->transactionTypes($types); } @@ -151,7 +151,7 @@ class JournalRepository implements JournalRepositoryInterface */ public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection { - $query = $this->user->transactionJournals()->expanded(); + $query = $this->user->transactionJournals()->expanded()->sortCorrectly(); $query->before($end); $query->after($start); From dc825d5a9ce127e714df66a16933d2b4a6036ef4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 08:40:22 +0200 Subject: [PATCH 097/206] Fix queries. --- app/Console/Commands/VerifyDatabase.php | 12 +++-- app/Helpers/Report/BalanceReportHelper.php | 51 ++++++++++---------- app/Repositories/Budget/BudgetRepository.php | 12 +++-- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 78001f33f4..ab8107bca2 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -126,7 +126,9 @@ class VerifyDatabase extends Command ::leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') ->leftJoin('users', 'budgets.user_id', '=', 'users.id') ->distinct() - ->get(['budgets.id', 'budgets.name', 'budget_transaction_journal.budget_id', 'budgets.user_id', 'users.email']); + ->whereNull('budget_transaction_journal.budget_id') + ->whereNull('budgets.deleted_at') + ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']); /** @var stdClass $entry */ foreach ($set as $entry) { @@ -145,7 +147,9 @@ class VerifyDatabase extends Command ::leftJoin('category_transaction_journal', 'categories.id', '=', 'category_transaction_journal.category_id') ->leftJoin('users', 'categories.user_id', '=', 'users.id') ->distinct() - ->get(['categories.id', 'categories.name', 'category_transaction_journal.category_id', 'categories.user_id', 'users.email']); + ->whereNull('category_transaction_journal.category_id') + ->whereNull('categories.deleted_at') + ->get(['categories.id', 'categories.name', 'categories.user_id', 'users.email']); /** @var stdClass $entry */ foreach ($set as $entry) { @@ -263,7 +267,9 @@ having transaction_count = 0 ::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id') ->leftJoin('users', 'tags.user_id', '=', 'users.id') ->distinct() - ->get(['tags.id', 'tags.tag', 'tag_transaction_journal.tag_id', 'tags.user_id', 'users.email']); + ->whereNull('tag_transaction_journal.tag_id') + ->whereNull('tags.deleted_at') + ->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']); /** @var stdClass $entry */ foreach ($set as $entry) { diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index d36bc46025..76e41779fe 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -20,6 +20,7 @@ use FireflyIII\Helpers\Collection\BalanceLine; use FireflyIII\Models\Account; use FireflyIII\Models\Budget; use FireflyIII\Models\Budget as BudgetModel; +use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -27,6 +28,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use Log; /** * Class BalanceReportHelper @@ -65,20 +67,22 @@ class BalanceReportHelper implements BalanceReportHelperInterface public function getBalanceReport(Carbon $start, Carbon $end, Collection $accounts): Balance { $balance = new Balance; - + Log::debug('Build new report.'); // build a balance header: - $header = new BalanceHeader; - // new Collection;// $this->budgetRepository->getBudgetsAndLimitsInRange($start, $end); // TO DO BUDGET getBudgets - $budgets = $this->budgetRepository->getBudgets(); - // new Collection; // $this->budgetRepository->spentPerBudgetPerAccount($budgets, $accounts, $start, $end); TO DO BUDGET journalsInPeriod - $spentData = $this->budgetRepository->journalsInPeriod($budgets, $accounts, $start, $end); + $header = new BalanceHeader; + $budgets = $this->budgetRepository->getBudgets(); + $limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end); + $spentData = $this->budgetRepository->journalsInPeriod($budgets, $accounts, $start, $end); foreach ($accounts as $account) { $header->addAccount($account); + Log::debug('Add account #' . $account->id . ' to header.'); } - /** @var BudgetModel $budget */ - foreach ($budgets as $budget) { - $balance->addBalanceLine($this->createBalanceLine($budget, $accounts, $spentData)); + /** @var LimitRepetition $repetition */ + foreach ($limitRepetitions as $repetition) { + $budget = $this->budgetRepository->find($repetition->budget_id); + Log::debug('Create and add balance line for budget #' . $budget->id); + $balance->addBalanceLine($this->createBalanceLine($budget, $repetition, $accounts)); } $balance->addBalanceLine($this->createEmptyBalanceLine($accounts, $spentData)); @@ -142,34 +146,30 @@ class BalanceReportHelper implements BalanceReportHelperInterface /** - * @param Budget $budget - * @param Collection $accounts - * @param Collection $spentData + * @param Budget $budget + * @param LimitRepetition $repetition + * @param Collection $accounts * * @return BalanceLine */ - private function createBalanceLine(BudgetModel $budget, Collection $accounts, Collection $spentData): BalanceLine + private function createBalanceLine(BudgetModel $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine { + Log::debug('Create line for budget #' . $budget->id . ' and repetition #' . $repetition->id); $line = new BalanceLine; + $budget->amount = $repetition->amount; $line->setBudget($budget); - $line->setStartDate($budget->startdate); // returned by getBudgetsAndLimitsInRange() - $line->setEndDate($budget->enddate); // returned by getBudgetsAndLimitsInRange() + + + $line->setStartDate($repetition->startdate); + $line->setEndDate($repetition->enddate); // loop accounts: foreach ($accounts as $account) { $balanceEntry = new BalanceEntry; $balanceEntry->setAccount($account); - - // get spent: - $entry = $spentData->filter( - function (TransactionJournal $model) use ($budget, $account) { - return $model->account_id == $account->id && $model->budget_id == $budget->id; - } + $spent = $this->budgetRepository->spentInPeriod( + new Collection([$budget]), new Collection([$account]), $repetition->startdate, $repetition->enddate ); - $spent = '0'; - if (!is_null($entry->first())) { - $spent = $entry->first()->spent; - } $balanceEntry->setSpent($spent); $line->addBalanceEntry($balanceEntry); } @@ -334,7 +334,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface $newSet->push($entry); } - $balance->setBalanceLines($newSet); return $balance; diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index a5a135ab2b..8881ecdf6d 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -868,6 +868,9 @@ class BudgetRepository implements BudgetRepositoryInterface { $return = new Collection; $accountIds = []; + // expand the number of grabbed fields: + $fields = TransactionJournal::queryFields(); + $fields[] = 'source.account_id'; if ($accounts->count() > 0) { $accountIds = $accounts->pluck('id')->toArray(); } @@ -878,16 +881,16 @@ class BudgetRepository implements BudgetRepositoryInterface ->before($end) ->sortCorrectly() ->after($start) + ->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->whereIn('budget_transaction_journal.budget_id', $budgets->pluck('id')->toArray()); // add account id's, if relevant: if (count($accountIds) > 0) { - $journalQuery->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id'); $journalQuery->whereIn('source.account_id', $accountIds); } // get them: $journals = $journalQuery->get(TransactionJournal::queryFields()); - Log::debug('journalsInPeriod journal count is ' . $journals->count()); + //Log::debug('journalsInPeriod journal count is ' . $journals->count()); // then get transactions themselves. $transactionQuery = $this->user->transactionjournals() @@ -897,13 +900,14 @@ class BudgetRepository implements BudgetRepositoryInterface ->after($start) ->leftJoin('transactions as related', 'related.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'related.id') + ->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id') ->whereIn('budget_transaction.budget_id', $budgets->pluck('id')->toArray()); if (count($accountIds) > 0) { - $transactionQuery->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id'); $transactionQuery->whereIn('source.account_id', $accountIds); } - $transactions = $transactionQuery->get(TransactionJournal::queryFields()); + + $transactions = $transactionQuery->get($fields); // return complete set: $return = $return->merge($transactions); From ed9acbdfdec9c4caf1b6064451eec0d06bf9f390 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 09:08:18 +0200 Subject: [PATCH 098/206] Reinstate report. --- app/Helpers/Report/BalanceReportHelper.php | 108 ++++++------------ app/Repositories/Budget/BudgetRepository.php | 58 +++++++++- .../Budget/BudgetRepositoryInterface.php | 10 ++ 3 files changed, 102 insertions(+), 74 deletions(-) diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index 76e41779fe..355db28dc0 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -84,10 +84,13 @@ class BalanceReportHelper implements BalanceReportHelperInterface Log::debug('Create and add balance line for budget #' . $budget->id); $balance->addBalanceLine($this->createBalanceLine($budget, $repetition, $accounts)); } + $noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end); + $coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end); + $leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine); - $balance->addBalanceLine($this->createEmptyBalanceLine($accounts, $spentData)); - $balance->addBalanceLine($this->createTagsBalanceLine($accounts, $start, $end)); - $balance->addBalanceLine($this->createDifferenceBalanceLine($accounts, $spentData, $start, $end)); + $balance->addBalanceLine($noBudgetLine); + $balance->addBalanceLine($coveredByTagLine); + $balance->addBalanceLine($leftUnbalancedLine); $balance->setBalanceHeader($header); // remove budgets without expenses from balance lines: @@ -155,7 +158,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface private function createBalanceLine(BudgetModel $budget, LimitRepetition $repetition, Collection $accounts): BalanceLine { Log::debug('Create line for budget #' . $budget->id . ' and repetition #' . $repetition->id); - $line = new BalanceLine; + $line = new BalanceLine; $budget->amount = $repetition->amount; $line->setBudget($budget); @@ -178,90 +181,55 @@ class BalanceReportHelper implements BalanceReportHelperInterface } /** - * @param Account $account - * @param Collection $spentData - * @param Collection $tagsLeft + * @param BalanceLine $noBudgetLine + * @param BalanceLine $coveredByTagLine * - * @return BalanceEntry + * @return BalanceLine */ - private function createDifferenceBalanceEntry(Account $account, Collection $spentData, Collection $tagsLeft): BalanceEntry + private function createLeftUnbalancedLine(BalanceLine $noBudgetLine, BalanceLine $coveredByTagLine): BalanceLine { - $entry = $spentData->filter( - function (TransactionJournal $model) use ($account) { - return $model->account_id == $account->id && is_null($model->budget_id); - } - ); - $spent = '0'; - if (!is_null($entry->first())) { - $spent = $entry->first()->spent; - } - $leftEntry = $tagsLeft->filter( - function (Tag $tag) use ($account) { - return $tag->account_id == $account->id; - } - ); - $left = '0'; - if (!is_null($leftEntry->first())) { - $left = $leftEntry->first()->sum; - } - $diffValue = bcadd($spent, $left); + $line = new BalanceLine; + $line->setRole(BalanceLine::ROLE_DIFFROLE); + $noBudgetEntries = $noBudgetLine->getBalanceEntries(); + $tagEntries = $coveredByTagLine->getBalanceEntries(); + + /** @var BalanceEntry $entry */ + foreach ($noBudgetEntries as $entry) { + $account = $entry->getAccount(); + $tagEntry = $tagEntries->filter( + function (BalanceEntry $current) use ($account) { + return $current->getAccount()->id === $account->id; + } + ); + if ($tagEntry->first()) { + // found corresponding entry. As we should: + $newEntry = new BalanceEntry; + $newEntry->setAccount($account); + $spent = bcadd($tagEntry->first()->getLeft(), $entry->getSpent()); + $newEntry->setSpent($spent); + $line->addBalanceEntry($newEntry); + } + } + + return $line; - // difference: - $diffEntry = new BalanceEntry; - $diffEntry->setAccount($account); - $diffEntry->setSpent($diffValue); - return $diffEntry; } /** * @param Collection $accounts - * @param Collection $spentData * @param Carbon $start * @param Carbon $end * - * - * * @return BalanceLine */ - private function createDifferenceBalanceLine(Collection $accounts, Collection $spentData, Carbon $start, Carbon $end): BalanceLine - { - $diff = new BalanceLine; - $tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end); - - $diff->setRole(BalanceLine::ROLE_DIFFROLE); - - /** @var Account $account */ - foreach ($accounts as $account) { - $diffEntry = $this->createDifferenceBalanceEntry($account, $spentData, $tagsLeft); - $diff->addBalanceEntry($diffEntry); - - } - - return $diff; - } - - /** - * @param Collection $accounts - * @param Collection $spentData - * - * @return BalanceLine - */ - private function createEmptyBalanceLine(Collection $accounts, Collection $spentData): BalanceLine + private function createNoBudgetLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine { $empty = new BalanceLine; foreach ($accounts as $account) { - $entry = $spentData->filter( - function (TransactionJournal $model) use ($account) { - return $model->account_id == $account->id && is_null($model->budget_id); - } - ); - $spent = '0'; - if (!is_null($entry->first())) { - $spent = $entry->first()->spent; - } - + $spent = $this->budgetRepository->spentInPeriodWithoutBudget(new Collection([$account]), $start, $end); + //$spent ='0'; // budget $budgetEntry = new BalanceEntry; $budgetEntry->setAccount($account); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 8881ecdf6d..0e6c80f587 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -13,6 +13,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; use Log; @@ -881,7 +882,12 @@ class BudgetRepository implements BudgetRepositoryInterface ->before($end) ->sortCorrectly() ->after($start) - ->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin( + 'transactions as source', + function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0'); + } + ) ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->whereIn('budget_transaction_journal.budget_id', $budgets->pluck('id')->toArray()); // add account id's, if relevant: @@ -900,7 +906,12 @@ class BudgetRepository implements BudgetRepositoryInterface ->after($start) ->leftJoin('transactions as related', 'related.transaction_journal_id', '=', 'transaction_journals.id') ->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'related.id') - ->leftJoin('transactions as source', 'source.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin( + 'transactions as source', + function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0'); + } + ) ->whereIn('budget_transaction.budget_id', $budgets->pluck('id')->toArray()); if (count($accountIds) > 0) { @@ -925,14 +936,25 @@ class BudgetRepository implements BudgetRepositoryInterface */ public function journalsInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): Collection { + $accountIds = []; + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + } + /** @var Collection $set */ - $set = $this->user + $query = $this->user ->transactionjournals() ->expanded() ->sortCorrectly() ->transactionTypes([TransactionType::WITHDRAWAL]) ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') ->whereNull('budget_transaction_journal.id') + ->leftJoin( + 'transactions as source', + function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', '0'); + } + ) ->before($end) ->after($start)->with( [ @@ -941,8 +963,14 @@ class BudgetRepository implements BudgetRepositoryInterface }, 'transactions.budgets', ] - )->get(TransactionJournal::queryFields()); + ); + // add account id's, if relevant: + if (count($accountIds) > 0) { + $query->whereIn('source.account_id', $accountIds); + } + + $set = $query->get(TransactionJournal::queryFields()); $set = $set->filter( function (TransactionJournal $journal) { foreach ($journal->transactions as $t) { @@ -1065,4 +1093,26 @@ class BudgetRepository implements BudgetRepositoryInterface return $limit; } + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string + { + $set = $this->journalsInPeriodWithoutBudget($accounts, $start, $end); + //Log::debug('spentInPeriod set count is ' . $set->count()); + $sum = '0'; + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $sum = bcadd($sum, TransactionJournal::amount($journal)); + } + + Log::debug('spentInPeriodWithoutBudget between ' . $start->format('Y-m-d') . ' and ' . $end->format('Y-m-d') . ' is ' . $sum); + + return $sum; + } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 780a3dbf2a..5c34a58bd7 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -53,6 +53,7 @@ interface BudgetRepositoryInterface /** * This method returns the oldest journal or transaction date known to this budget. * Will cache result. + * * @param Budget $budget * * @return Carbon @@ -218,6 +219,15 @@ interface BudgetRepositoryInterface */ public function journalsInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): Collection; + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param Collection $budgets * @param Collection $accounts From e1c146a5c18c2de54ae169c80aae2f74316bca25 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 09:17:47 +0200 Subject: [PATCH 099/206] Reinstate chart. --- .../Controllers/Chart/CategoryController.php | 89 +++++++++---------- app/Http/routes.php | 2 +- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index d9f8d518e2..c89e4b0110 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -9,6 +9,7 @@ use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Log; @@ -264,6 +265,7 @@ class CategoryController extends Controller * $entries->push($entry); * } * // generate chart with data: + * * $data = $this->generator->multiYear($entries); * $cache->store($data); * @@ -275,60 +277,55 @@ class CategoryController extends Controller /** * @param Category $category - * @param string $reportType * @param Carbon $start * @param Carbon $end * @param Collection $accounts * * @return \Illuminate\Http\JsonResponse */ - public function period(Category $category, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function period(Category $category, Carbon $start, Carbon $end, Collection $accounts) { - /** - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($reportType); - * $cache->addProperty($accounts); - * $cache->addProperty($category->id); - * $cache->addProperty('category'); - * $cache->addProperty('period'); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * /** @var CategoryRepositoryInterface $repository - * $repository = app(CategoryRepositoryInterface::class); - * // loop over period, add by users range: - * $current = clone $start; - * $viewRange = Preferences::get('viewRange', '1M')->data; - * $format = strval(trans('config.month')); - * $set = new Collection; - * while ($current < $end) { - * $currentStart = clone $current; - * $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); - * - * $spent = strval(array_sum($repository->spentPerDay($category, $currentStart, $currentEnd, $accounts))); - * $earned = strval(array_sum($repository->earnedPerDay($category, $currentStart, $currentEnd, $accounts))); - * - * $entry = [ - * $category->name, - * $currentStart->formatLocalized($format), - * $spent, - * $earned, - * - * ]; - * $set->push($entry); - * $currentEnd->addDay(); - * $current = clone $currentEnd; - * } - * $data = $this->generator->period($set); - * $cache->store($data); - * - * return Response::json($data); - * **/ + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($accounts); + $cache->addProperty($category->id); + $cache->addProperty('category'); + $cache->addProperty('period'); + if ($cache->has()) { + // return Response::json($cache->get()); + } + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); + $categoryCollection = new Collection([$category]); + // loop over period, add by users range: + $current = clone $start; + $viewRange = Preferences::get('viewRange', '1M')->data; + $format = strval(trans('config.month')); + $set = new Collection; + while ($current < $end) { + $currentStart = clone $current; + $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); + $spent = $repository->spentInPeriod($categoryCollection, $accounts, $currentStart, $currentEnd); + $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $currentStart, $currentEnd); + + $entry = [ + $category->name, + $currentStart->formatLocalized($format), + $spent, + $earned, + + ]; + $set->push($entry); + $currentEnd->addDay(); + $current = clone $currentEnd; + } + $data = $this->generator->period($set); + $cache->store($data); + + return Response::json($data); } /** diff --git a/app/Http/routes.php b/app/Http/routes.php index 55a53c7e17..8415a709ad 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -208,7 +208,7 @@ Route::group( // categories: Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']); - Route::get('/chart/category/period/{category}/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@period']); + Route::get('/chart/category/period/{category}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@period']); // these three charts are for reports: Route::get('/chart/category/earned-in-period/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@earnedInPeriod']); From d2b4bd78a9508cd0ede374352931195690b9df8b Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 10:02:27 +0200 Subject: [PATCH 100/206] Removed some dead code. --- app/Http/Controllers/CategoryController.php | 2 +- .../Controllers/Chart/CategoryController.php | 218 ++---- app/Http/routes.php | 6 +- app/Repositories/Budget/BudgetRepository.php | 692 ------------------ .../Category/CategoryRepository.php | 524 +------------ .../Category/CategoryRepositoryInterface.php | 176 +---- public/js/ff/reports/default/year.js | 5 - 7 files changed, 107 insertions(+), 1516 deletions(-) diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index ee0aac585e..696145827b 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -136,7 +136,7 @@ class CategoryController extends Controller $start = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ $end = session('end', Carbon::now()->startOfMonth()); - $list = $repository->journalsInPeriodWithoutCategory(new Collection(), $start, $end); + $list = $repository->journalsInPeriodWithoutCategory(new Collection(), [], $start, $end); $subTitle = trans( 'firefly.without_category_between', ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index c89e4b0110..b6ba76a36c 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -102,46 +102,6 @@ class CategoryController extends Controller return Response::json($data); } - /** - * Returns a chart of what has been earned in this period in each category - * grouped by month. - * - * @param CRI $repository - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return \Illuminate\Http\JsonResponse - */ - public function earnedInPeriod(CRI $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - /** - * $cache = new CacheProperties; // chart properties for cache: - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($reportType); - * $cache->addProperty($accounts); - * $cache->addProperty('category'); - * $cache->addProperty('earned-in-period'); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * $set = $repository->earnedForAccountsPerMonth($accounts, $start, $end); - * $categories = $set->unique('id')->sortBy( - * function (Category $category) { - * return $category->name; - * } - * ); - * $entries = $this->filterCollection($start, $end, $set, $categories); - * $data = $this->generator->earnedInPeriod($categories, $entries); - * $cache->store($data); - * - * return $data; - **/ - } - /** * Show this month's category overview. * @@ -196,82 +156,67 @@ class CategoryController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function multiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) + public function multiYear(Carbon $start, Carbon $end, Collection $accounts, Collection $categories) { - /** - * // /** @var CRI $repository - * // $repository = app(CRI::class); - * - * // chart properties for cache: - * $cache = new CacheProperties(); - * $cache->addProperty($reportType); - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($accounts); - * $cache->addProperty($categories); - * $cache->addProperty('multiYearCategory'); - * - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * $entries = new Collection; - * $set = $repository->listMultiYear($categories, $accounts, $start, $end); - * - * /** @var Category $category - * foreach ($categories as $category) { - * $entry = ['name' => '', 'spent' => [], 'earned' => []]; - * - * $currentStart = clone $start; - * while ($currentStart < $end) { - * // fix the date: - * $year = $currentStart->year; - * $currentEnd = clone $currentStart; - * $currentEnd->endOfYear(); - * - * - * // get data: - * if (is_null($category->id)) { - * $name = trans('firefly.noCategory'); - * $spent = $repository->sumSpentNoCategory($accounts, $currentStart, $currentEnd); - * $earned = $repository->sumEarnedNoCategory($accounts, $currentStart, $currentEnd); - * } else { - * // get from set: - * $entrySpent = $set->filter( - * function (Category $cat) use ($year, $category) { - * return ($cat->type == 'Withdrawal' && $cat->dateFormatted == $year && $cat->id == $category->id); - * } - * )->first(); - * $entryEarned = $set->filter( - * function (Category $cat) use ($year, $category) { - * return ($cat->type == 'Deposit' && $cat->dateFormatted == $year && $cat->id == $category->id); - * } - * )->first(); - * - * $name = $category->name; - * $spent = !is_null($entrySpent) ? $entrySpent->sum : 0; - * $earned = !is_null($entryEarned) ? $entryEarned->sum : 0; - * } - * - * // save to array: - * $entry['name'] = $name; - * $entry['spent'][$year] = ($spent * -1); - * $entry['earned'][$year] = $earned; - * - * // jump to next year. - * $currentStart = clone $currentEnd; - * $currentStart->addDay(); - * } - * $entries->push($entry); - * } - * // generate chart with data: - * - * $data = $this->generator->multiYear($entries); - * $cache->store($data); - * - * return Response::json($data); - * - */ + + /** @var CRI $repository */ + $repository = app(CRI::class); + + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($accounts); + $cache->addProperty($categories); + $cache->addProperty('multiYearCategory'); + + if ($cache->has()) { + //return Response::json($cache->get()); + } + + $entries = new Collection; + + /** @var Category $category */ + foreach ($categories as $category) { + $entry = ['name' => '', 'spent' => [], 'earned' => []]; + + $currentStart = clone $start; + while ($currentStart < $end) { + // fix the date: + $year = $currentStart->year; + $currentEnd = clone $currentStart; + $currentEnd->endOfYear(); + + // get data: + if (is_null($category->id)) { + $name = trans('firefly.noCategory'); + $spent = $repository->spentInPeriodWithoutCategory($accounts, $currentStart, $currentEnd); + $earned = $repository->earnedInPeriodWithoutCategory($accounts, $currentStart, $currentEnd); + } else { + + $name = $category->name; + $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd); + $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd); + } + + // save to array: + $entry['name'] = $name; + $entry['spent'][$year] = ($spent * -1); + $entry['earned'][$year] = $earned; + + // jump to next year. + $currentStart = clone $currentEnd; + $currentStart->addDay(); + } + $entries->push($entry); + } + // generate chart with data: + + $data = $this->generator->multiYear($entries); + $cache->store($data); + + return Response::json($data); + } @@ -347,49 +292,6 @@ class CategoryController extends Controller return Response::json($data); } - /** - * Returns a chart of what has been spent in this period in each category - * grouped by month. - * - * @param CRI $repository - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * - * @return \Illuminate\Http\JsonResponse - */ - public function spentInPeriod(CRI $repository, $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - /** - * $cache = new CacheProperties; // chart properties for cache: - * $cache->addProperty($start); - * $cache->addProperty($end); - * $cache->addProperty($reportType); - * $cache->addProperty($accounts); - * $cache->addProperty('category'); - * $cache->addProperty('spent-in-period'); - * if ($cache->has()) { - * return Response::json($cache->get()); - * } - * - * - * $set = $repository->spentForAccountsPerMonth($accounts, $start, $end); - * $categories = $set->unique('id')->sortBy( - * function (Category $category) { - * return $category->name; - * } - * ); - * $entries = $this->filterCollection($start, $end, $set, $categories); - * $entries = $this->invertSelection($entries); - * $data = $this->generator->spentInPeriod($categories, $entries); - * $cache->store($data); - * - * return $data; - * */ - } - /** * @param Carbon $start * @param Carbon $end diff --git a/app/Http/routes.php b/app/Http/routes.php index 8415a709ad..aea15c53ab 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -211,11 +211,7 @@ Route::group( Route::get('/chart/category/period/{category}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@period']); // these three charts are for reports: - Route::get('/chart/category/earned-in-period/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@earnedInPeriod']); - Route::get('/chart/category/spent-in-period/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@spentInPeriod']); - Route::get( - '/chart/category/multi-year/{reportType}/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear'] - ); + Route::get('/chart/category/multi-year/default/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear']); Route::get('/chart/category/{category}/period', ['uses' => 'Chart\CategoryController@currentPeriod']); Route::get('/chart/category/{category}/period/{date}', ['uses' => 'Chart\CategoryController@specificPeriod']); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 0e6c80f587..9e17c3c244 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -37,31 +37,6 @@ class BudgetRepository implements BudgetRepositoryInterface $this->user = $user; } - // /** - // * @param Budget $budget - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return string - // */ - // public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): string - // { - // return $this->commonBalanceInPeriod($budget, $start, $end, $accounts); - // } - - // /** - // * @return bool - // */ - // public function cleanupBudgets(): bool - // { - // // delete limits with amount 0: - // BudgetLimit::where('amount', 0)->delete(); - // - // return true; - // - // } - /** * @param Budget $budget * @@ -74,23 +49,6 @@ class BudgetRepository implements BudgetRepositoryInterface return true; } - // /** - // * @param Budget $budget - // * @param Account $account - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection - // { - // return $budget->transactionjournals()->expanded() - // ->before($end) - // ->after($start) - // ->where('source_account.id', $account->id) - // ->get(TransactionJournal::queryFields()); - // } - /** * Find a budget. * @@ -108,21 +66,6 @@ class BudgetRepository implements BudgetRepositoryInterface return $budget; } - // /** - // * @param Budget $budget - // * - // * @return Carbon - // */ - // public function firstActivity(Budget $budget): Carbon - // { - // $first = $budget->transactionjournals()->orderBy('date', 'ASC')->first(); - // if ($first) { - // return $first->date; - // } - // - // return new Carbon; - // } - /** * This method returns the oldest journal or transaction date known to this budget. * Will cache result. @@ -168,62 +111,6 @@ class BudgetRepository implements BudgetRepositoryInterface return $set; } - // /** - // * @param Account $account - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return Collection - // */ - // public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // $ids = $accounts->pluck('id')->toArray(); - // - // return $this->user - // ->transactionjournals() - // ->expanded() - // ->where('source_account.id', $account->id) - // ->whereNotIn('destination_account.id', $ids) - // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->whereNull('budget_transaction_journal.id') - // ->before($end) - // ->after($start) - // ->get(TransactionJournal::queryFields()); - // } - - // /** - // * Get the budgeted amounts for each budgets in each year. - // * - // * @param Collection $budgets - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection - // { - // $budgetIds = $budgets->pluck('id')->toArray(); - // - // $set = $this->user->budgets() - // ->leftJoin('budget_limits', 'budgets.id', '=', 'budget_limits.budget_id') - // ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - // ->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')) - // ->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d')) - // ->groupBy('budgets.id') - // ->groupBy('dateFormatted') - // ->whereIn('budgets.id', $budgetIds) - // ->get( - // [ - // 'budgets.*', - // DB::raw('DATE_FORMAT(`limit_repetitions`.`startdate`,"%Y") as `dateFormatted`'), - // DB::raw('SUM(`limit_repetitions`.`amount`) as `budgeted`'), - // ] - // ); - // - // return $set; - // } - /** * @param Carbon $start * @param Carbon $end @@ -244,275 +131,6 @@ class BudgetRepository implements BudgetRepositoryInterface return $set; } - // /** - // * Returns an array with every budget in it and the expenses for each budget - // * per month. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return array - // */ - // public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array - // { - // $ids = $accounts->pluck('id')->toArray(); - // - // /** @var Collection $set */ - // $set = $this->user->budgets() - // ->leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - // ->leftJoin( - // 'transactions', function (JoinClause $join) { - // $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - // } - // ) - // ->groupBy('budgets.id') - // ->groupBy('dateFormatted') - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - // ->whereIn('transactions.account_id', $ids) - // ->get( - // [ - // 'budgets.*', - // DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y-%m") AS `dateFormatted`'), - // DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - // ] - // ); - // - // $set = $set->sortBy( - // function (Budget $budget) { - // return strtolower($budget->name); - // } - // ); - // - // $return = []; - // foreach ($set as $budget) { - // $id = $budget->id; - // if (!isset($return[$id])) { - // $return[$id] = [ - // 'budget' => $budget, - // 'entries' => [], - // ]; - // } - // // store each entry: - // $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; - // } - // - // return $return; - // } - - // /** - // * Returns an array with every budget in it and the expenses for each budget - // * per year for. - // * - // * @param Collection $budgets - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @deprecated - // * - // * @return array - // */ - // public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array - // { - // // get budgets, - // $ids = $accounts->pluck('id')->toArray(); - // $budgetIds = $budgets->pluck('id')->toArray(); - // - // /** @var Collection $set */ - // $set = $this->user->budgets() - // ->join('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - // ->leftJoin( - // 'transactions', function (JoinClause $join) { - // $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - // } - // ) - // ->groupBy('budgets.id') - // ->groupBy('dateFormatted') - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - // ->whereIn('transactions.account_id', $ids) - // ->whereIn('budgets.id', $budgetIds) - // ->get( - // [ - // 'budgets.*', - // DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), - // DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - // ] - // ); - // - // // run it again, for transactions this time. - // /** @var Collection $secondSet */ - // $secondSet = $this->user->budgets() - // ->join('budget_transaction', 'budgets.id', '=', 'budget_transaction.budget_id') - // ->leftJoin('transactions', 'transactions.id', '=', 'budget_transaction.transaction_id') - // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - // ->where('transactions.amount', '<', 0) - // ->groupBy('budgets.id') - // ->groupBy('dateFormatted') - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - // ->whereIn('transactions.account_id', $ids) - // ->whereIn('budgets.id', $budgetIds) - // ->get( - // [ - // 'budgets.*', - // DB::raw('DATE_FORMAT(`transaction_journals`.`date`, "%Y") AS `dateFormatted`'), - // DB::raw('SUM(`transactions`.`amount`) AS `sumAmount`'), - // ] - // ); - // - // $set = $set->sortBy( - // function (Budget $budget) { - // return strtolower($budget->name); - // } - // ); - // - // $return = []; - // foreach ($set as $budget) { - // Log::debug('First set, budget #' . $budget->id . ' (' . $budget->name . ')'); - // $id = $budget->id; - // if (!isset($return[$id])) { - // Log::debug('$return[$id] is not set, now created.'); - // $return[$id] = [ - // 'budget' => $budget, - // 'entries' => [], - // ]; - // } - // Log::debug('Add new entry to entries, for ' . $budget->dateFormatted . ' and amount ' . $budget->sumAmount); - // // store each entry: - // $return[$id]['entries'][$budget->dateFormatted] = $budget->sumAmount; - // } - // unset($budget); - // - // // run the second set: - // foreach ($secondSet as $entry) { - // $id = $entry->id; - // // create it if it still does not exist (not really likely) - // if (!isset($return[$id])) { - // $return[$id] = [ - // 'budget' => $entry, - // 'entries' => [], - // ]; - // } - // // this one might be filled too: - // $startAmount = $return[$id]['entries'][$entry->dateFormatted] ?? '0'; - // // store each entry: - // $return[$id]['entries'][$entry->dateFormatted] = bcadd($startAmount, $entry->sumAmount); - // } - // - // return $return; - // } - - // /** - // * Returns a list of budgets, budget limits and limit repetitions - // * (doubling any of them in a left join) - // * - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection - // { - // /** @var Collection $set */ - // $set = $this->user - // ->budgets() - // ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') - // ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - // ->where( - // function (Builder $query) use ($start, $end) { - // $query->where( - // function (Builder $query) use ($start, $end) { - // $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')); - // $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d')); - // } - // ); - // $query->orWhere( - // function (Builder $query) { - // $query->whereNull('limit_repetitions.startdate'); - // $query->whereNull('limit_repetitions.enddate'); - // } - // ); - // } - // ) - // ->orderBy('budgets.id', 'budget_limits.startdate', 'limit_repetitions.enddate') - // ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']); - // - // $set = $set->sortBy( - // function (Budget $budget) { - // return strtolower($budget->name); - // } - // ); - // - // return $set; - // - // } - - // /** - // * @param Budget $budget - // * @param string $repeatFreq - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return LimitRepetition - // */ - // public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition - // { - // $data = $budget->limitrepetitions() - // ->where('budget_limits.repeat_freq', $repeatFreq) - // ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) - // ->where('limit_repetitions.enddate', $end->format('Y-m-d 00:00:00')) - // ->first(['limit_repetitions.*']); - // if (is_null($data)) { - // return new LimitRepetition; - // } - // - // return $data; - // } - - // /** - // * Returns all expenses for the given budget and the given accounts, in the given period. - // * - // * @param Budget $budget - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection - // { - // $ids = $accounts->pluck('id')->toArray(); - // $set = $budget->transactionjournals() - // ->before($end) - // ->after($start) - // ->expanded() - // ->where('transaction_types.type', TransactionType::WITHDRAWAL) - // ->whereIn('source_account.id', $ids) - // ->get(TransactionJournal::queryFields()); - // - // return $set; - // } - - // /** - // * @param Budget $budget - // * - // * @return Carbon - // */ - // public function getFirstBudgetLimitDate(Budget $budget): Carbon - // { - // $limit = $budget->budgetlimits()->orderBy('startdate', 'ASC')->first(); - // if ($limit) { - // return $limit->startdate; - // } - // - // return Carbon::now()->startOfYear(); - // } - /** * @return Collection */ @@ -530,316 +148,6 @@ class BudgetRepository implements BudgetRepositoryInterface return $set; } - // /** - // * Returns all the transaction journals for a limit, possibly limited by a limit repetition. - // * - // * @param Budget $budget - // * @param LimitRepetition $repetition - // * @param int $take - // * - // * @return LengthAwarePaginator - // */ - // public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator - // { - // $offset = intval(Input::get('page')) > 0 ? intval(Input::get('page')) * $take : 0; - // $setQuery = $budget->transactionjournals()->expanded() - // ->take($take)->offset($offset) - // ->orderBy('transaction_journals.date', 'DESC') - // ->orderBy('transaction_journals.order', 'ASC') - // ->orderBy('transaction_journals.id', 'DESC'); - // $countQuery = $budget->transactionjournals(); - // - // - // if (!is_null($repetition->id)) { - // $setQuery->after($repetition->startdate)->before($repetition->enddate); - // $countQuery->after($repetition->startdate)->before($repetition->enddate); - // } - // - // - // $set = $setQuery->get(TransactionJournal::queryFields()); - // $count = $countQuery->count(); - // - // - // $paginator = new LengthAwarePaginator($set, $count, $take, $offset); - // - // return $paginator; - // } - - // /** - // * Returns a list of budget limits that are valid in the current given range. - // * $ignore is optional. Send an empty limit rep. - // * - // * @param Budget $budget - // * @param Carbon $start - // * @param Carbon $end - // * @param LimitRepetition $ignore - // * - // * @return Collection - // */ - // public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection - // { - // $query = $budget->limitrepetitions() - // // starts before start time, and the end also after start time. - // ->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00')) - // ->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00')); - // if (!is_null($ignore->id)) { - // $query->where('limit_repetitions.id', '!=', $ignore->id); - // } - // $data = $query->get(['limit_repetitions.*']); - // - // return $data; - // } - - // /** - // * @param Carbon $start - // * @param Carbon $end - // * @param int $page - // * @param int $pageSize - // * - // * @return LengthAwarePaginator - // */ - // public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator - // { - // $offset = ($page - 1) * $pageSize; - // $query = $this->user - // ->transactionjournals() - // ->expanded() - // ->where('transaction_types.type', TransactionType::WITHDRAWAL) - // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->whereNull('budget_transaction_journal.id') - // ->before($end) - // ->after($start); - // - // $count = $query->count(); - // $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); - // $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); - // - // return $paginator; - // } - - // /** - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // $ids = $accounts->pluck('id')->toArray(); - // - // return $this->user - // ->transactionjournals() - // ->expanded() - // ->whereIn('source_account.id', $ids) - // ->where('transaction_types.type', TransactionType::WITHDRAWAL) - // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->whereNull('budget_transaction_journal.id') - // ->before($end) - // ->after($start) - // ->get(TransactionJournal::queryFields()); - // } - - // /** - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return string - // */ - // public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string - // { - // $ids = $accounts->pluck('id')->toArray(); - // $entry = $this->user - // ->transactionjournals() - // ->whereNotIn( - // 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { - // $query - // ->select('transaction_journals.id') - // ->from('transaction_journals') - // ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) - // ->whereNotNull('budget_transaction_journal.budget_id'); - // } - // ) - // ->after($start) - // ->before($end) - // ->leftJoin( - // 'transactions', function (JoinClause $join) { - // $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - // } - // ) - // ->whereIn('transactions.account_id', $ids) - // //->having('transaction_count', '=', 1) TO DO check if this still works - // ->transactionTypes([TransactionType::WITHDRAWAL]) - // ->first( - // [ - // DB::raw('SUM(`transactions`.`amount`) as `journalAmount`'), - // DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), - // ] - // ); - // if (is_null($entry)) { - // return '0'; - // } - // if (is_null($entry->journalAmount)) { - // return '0'; - // } - // - // return $entry->journalAmount; - // } - - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * That array contains: - // * - // * budgetid: - // * - // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget - // * from the given users accounts.. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return array - // */ - // public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array - // { - // $ids = $accounts->pluck('id')->toArray(); - // /** @var Collection $query */ - // $query = $this->user->transactionJournals() - // ->transactionTypes([TransactionType::WITHDRAWAL]) - // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - // ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - // ->whereIn('transactions.account_id', $ids) - // ->where('transactions.amount', '<', 0) - // ->before($end) - // ->after($start) - // ->groupBy('budget_id') - // ->groupBy('dateFormatted') - // ->get( - // ['transaction_journals.date as dateFormatted', 'budget_transaction_journal.budget_id', - // DB::raw('SUM(`transactions`.`amount`) AS `sum`')] - // ); - // - // $return = []; - // foreach ($query->toArray() as $entry) { - // $budgetId = $entry['budget_id']; - // if (!isset($return[$budgetId])) { - // $return[$budgetId] = []; - // } - // $return[$budgetId][$entry['dateFormatted']] = $entry['sum']; - // } - // - // return $return; - // } - - // /** - // * Returns a list of expenses (in the field "spent", grouped per budget per account. - // * - // * @param Collection $budgets - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // $accountIds = $accounts->pluck('id')->toArray(); - // $budgetIds = $budgets->pluck('id')->toArray(); - // $set = $this->user->transactionjournals() - // ->leftJoin( - // 'transactions AS t_from', function (JoinClause $join) { - // $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); - // } - // ) - // ->leftJoin( - // 'transactions AS t_to', function (JoinClause $join) { - // $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); - // } - // ) - // ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - // ->whereIn('t_from.account_id', $accountIds) - // ->whereNotIn('t_to.account_id', $accountIds) - // ->where( - // function (Builder $q) use ($budgetIds) { - // $q->whereIn('budget_transaction_journal.budget_id', $budgetIds); - // $q->orWhereNull('budget_transaction_journal.budget_id'); - // } - // ) - // ->after($start) - // ->before($end) - // ->groupBy('t_from.account_id') - // ->groupBy('budget_transaction_journal.budget_id') - // ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])// opening balance is not an expense. - // ->get( - // [ - // 't_from.account_id', 'budget_transaction_journal.budget_id', - // DB::raw('SUM(`t_from`.`amount`) AS `spent`'), - // ] - // ); - // - // return $set; - // - // } - - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $budget - // * from all the users accounts. - // * - // * @param Budget $budget - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return array - // */ - // public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array - // { - // /** @var Collection $query */ - // $query = $budget->transactionjournals() - // ->transactionTypes([TransactionType::WITHDRAWAL]) - // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - // ->where('transactions.amount', '<', 0) - // ->before($end) - // ->after($start) - // ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); - // - // $return = []; - // foreach ($query->toArray() as $entry) { - // $return[$entry['dateFormatted']] = $entry['sum']; - // } - // - // // also search transactions: - // $query = $budget->transactions() - // ->transactionTypes([TransactionType::WITHDRAWAL]) - // ->where('transactions.amount', '<', 0) - // ->before($end) - // ->after($start) - // ->groupBy('dateFormatted')->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`transactions`.`amount`) AS `sum`')]); - // foreach ($query as $newEntry) { - // // add to return array. - // $date = $newEntry['dateFormatted']; - // if (isset($return[$date])) { - // $return[$date] = bcadd($newEntry['sum'], $return[$date]); - // continue; - // } - // - // $return[$date] = $newEntry['sum']; - // } - // - // return $return; - // } - /** * @return Collection */ diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index f76c5fc3bb..7c1eff2d91 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -18,10 +18,6 @@ use Illuminate\Support\Collection; */ class CategoryRepository implements CategoryRepositoryInterface { - // const SPENT = 1; - // const EARNED = 2; - - /** @var User */ private $user; @@ -35,280 +31,6 @@ class CategoryRepository implements CategoryRepositoryInterface $this->user = $user; } - // /** - // * Returns a collection of Categories appended with the amount of money that has been earned - // * in these categories, based on the $accounts involved, in period X, grouped per month. - // * The amount earned in category X in period X is saved in field "earned". - // * - // * @param $accounts - // * @param $start - // * @param $end - // * - // * @return Collection - // */ - // public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // - // $collection = $this->user->categories() - // ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - // ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - // ->leftJoin( - // 'transactions AS t_src', function (JoinClause $join) { - // $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); - // } - // ) - // ->leftJoin( - // 'transactions AS t_dest', function (JoinClause $join) { - // $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); - // } - // ) - // ->whereIn('t_dest.account_id', $accounts->pluck('id')->toArray())// to these accounts (earned) - // ->whereNotIn('t_src.account_id', $accounts->pluck('id')->toArray())//-- but not from these accounts - // ->whereIn( - // 'transaction_types.type', [TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] - // ) - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - // ->groupBy('categories.id') - // ->groupBy('dateFormatted') - // ->get( - // [ - // 'categories.*', - // DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), - // DB::raw('SUM(`t_dest`.`amount`) AS `earned`'), - // ] - // ); - // - // return $collection; - // - // - // } - - // /** - // * @param Category $category - // * @param Carbon|null $start - // * @param Carbon|null $end - // * - // * @return int - // */ - // public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int - // { - // $query = $category->transactionjournals(); - // if (!is_null($start)) { - // $query->after($start); - // } - // if (!is_null($end)) { - // $query->before($end); - // } - // - // return $query->count(); - // - // } - - // /** - // * This method returns a very special collection for each category: - // * - // * category, year, expense/earned, amount - // * - // * categories can be duplicated. - // * - // * @param Collection $categories - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // - // $set = $this->user->categories() - // ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'category_transaction_journal.transaction_journal_id') - // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - // ->whereIn('transaction_types.type', [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL]) - // ->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()) - // ->whereIn('categories.id', $categories->pluck('id')->toArray()) - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - // ->groupBy('categories.id') - // ->groupBy('transaction_types.type') - // ->groupBy('dateFormatted') - // ->get( - // [ - // 'categories.*', - // DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y") as `dateFormatted`'), - // 'transaction_types.type', - // DB::raw('SUM(`amount`) as `sum`'), - // ] - // ); - // - // return $set; - // - // } - - // /** - // * Returns a list of transaction journals in the range (all types, all accounts) that have no category - // * associated to them. - // * - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function listNoCategory(Carbon $start, Carbon $end): Collection - // { - // return $this->user - // ->transactionjournals() - // ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->whereNull('category_transaction_journal.id') - // ->before($end) - // ->after($start) - // ->orderBy('transaction_journals.date', 'DESC') - // ->orderBy('transaction_journals.order', 'ASC') - // ->orderBy('transaction_journals.id', 'DESC') - // ->get(['transaction_journals.*']); - // } - - // /** - // * Returns a collection of Categories appended with the amount of money that has been spent - // * in these categories, based on the $accounts involved, in period X, grouped per month. - // * The amount spent in category X in period X is saved in field "spent". - // * - // * @param $accounts - // * @param $start - // * @param $end - // * - // * @return Collection - // */ - // public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // $accountIds = $accounts->pluck('id')->toArray(); - // $query = $this->user->categories() - // ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') - // ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - // ->leftJoin( - // 'transactions AS t_src', function (JoinClause $join) { - // $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); - // } - // ) - // ->leftJoin( - // 'transactions AS t_dest', function (JoinClause $join) { - // $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); - // } - // ) - // ->whereIn( - // 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] - // )// spent on these things. - // ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - // ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - // ->groupBy('categories.id') - // ->groupBy('dateFormatted'); - // - // if (count($accountIds) > 0) { - // $query->whereIn('t_src.account_id', $accountIds)// from these accounts (spent) - // ->whereNotIn('t_dest.account_id', $accountIds);//-- but not from these accounts (spent internally) - // } - // - // $collection = $query->get( - // [ - // 'categories.*', - // DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") as `dateFormatted`'), - // DB::raw('SUM(`t_src`.`amount`) AS `spent`'), - // ] - // ); - // - // return $collection; - // } - - // /** - // * Returns the total amount of money related to transactions without any category connected to - // * it. Returns either the earned amount. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return string - // */ - // public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string - // { - // return $this->sumNoCategory($accounts, $start, $end, self::EARNED); - // } - - // /** - // * Returns the total amount of money related to transactions without any category connected to - // * it. Returns either the spent amount. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return string - // */ - // public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string - // { - // $sum = $this->sumNoCategory($accounts, $start, $end, self::SPENT); - // if (is_null($sum)) { - // return '0'; - // } - // - // return $sum; - // } - - // /** - // * Returns the total amount of money related to transactions without any category connected to - // * it. Returns either the earned or the spent amount. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * @param int $group - // * - // * @return string - // */ - // protected function sumNoCategory(Collection $accounts, Carbon $start, Carbon $end, $group = self::EARNED) - // { - // $accountIds = $accounts->pluck('id')->toArray(); - // if ($group == self::EARNED) { - // $types = [TransactionType::DEPOSIT]; - // } else { - // $types = [TransactionType::WITHDRAWAL]; - // } - // - // // is withdrawal or transfer AND account_from is in the list of $accounts - // $query = $this->user - // ->transactionjournals() - // ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - // ->whereNull('category_transaction_journal.id') - // ->before($end) - // ->after($start) - // ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - // ->having('transaction_count', '=', 1) - // ->transactionTypes($types); - // - // if (count($accountIds) > 0) { - // $query->whereIn('transactions.account_id', $accountIds); - // } - // - // - // $single = $query->first( - // [ - // DB::raw('SUM(`transactions`.`amount`) as `sum`'), - // DB::raw('COUNT(`transactions`.`id`) as `transaction_count`'), - // ] - // ); - // if (!is_null($single)) { - // return $single->sum; - // } - // - // return '0'; - // - // } - /** * @param Category $category * @@ -321,53 +43,6 @@ class CategoryRepository implements CategoryRepositoryInterface return true; } - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category - // * from all the users $accounts. - // * - // * @param Category $category - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return array - // */ - // public function earnedPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array - // { - // /** @var Collection $query */ - // $query = $category->transactionjournals() - // ->expanded() - // ->transactionTypes([TransactionType::DEPOSIT]) - // ->before($end) - // ->after($start) - // ->groupBy('transaction_journals.date'); - // - // $query->leftJoin( - // 'transactions as destination', function (JoinClause $join) { - // $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); - // } - // ); - // - // - // if ($accounts->count() > 0) { - // $ids = $accounts->pluck('id')->toArray(); - // $query->whereIn('destination.account.id', $ids); - // } - // - // $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`destination`.`amount`) AS `sum`')]); - // - // $return = []; - // foreach ($result->toArray() as $entry) { - // $return[$entry['dateFormatted']] = $entry['sum']; - // } - // - // return $return; - // } - /** * @param Collection $categories * @param Collection $accounts @@ -405,162 +80,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $category; } - // /** - // * @param Category $category - // * - // * @return Carbon - // */ - // public function getFirstActivityDate(Category $category): Carbon - // { - // /** @var TransactionJournal $first */ - // $first = $category->transactionjournals()->orderBy('date', 'ASC')->first(); - // if ($first) { - // return $first->date; - // } - // - // return new Carbon; - // - // } - - // /** - // * @param Category $category - // * @param int $page - // * @param int $pageSize - // * - // * @return Collection - // */ - // public function getJournals(Category $category, int $page, int $pageSize = 50): Collection - // { - // $offset = $page > 0 ? $page * $pageSize : 0; - // - // return $category->transactionjournals()->expanded()->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); - // - // } - // - // /** - // * @param Category $category - // * @param Collection $accounts - // * - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection - // { - // $ids = $accounts->pluck('id')->toArray(); - // - // return $category->transactionjournals() - // ->after($start) - // ->before($end) - // ->expanded() - // ->whereIn('source_account.id', $ids) - // ->whereNotIn('destination_account.id', $ids) - // ->get(TransactionJournal::queryFields()); - // } - - // /** - // * @param Category $category - // * @param Carbon $start - // * @param Carbon $end - // * @param int $page - // * @param int $pageSize - // * - // * - // * @return Collection - // */ - // public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection - // { - // $offset = $page > 0 ? $page * $pageSize : 0; - // - // return $category->transactionjournals() - // ->after($start) - // ->before($end) - // ->expanded() - // ->take($pageSize) - // ->offset($offset) - // ->get(TransactionJournal::queryFields()); - // } - - // /** - // * @param Category $category - // * - // * @return Carbon - // */ - // public function getLatestActivity(Category $category): Carbon - // { - // $first = new Carbon('1900-01-01'); - // $second = new Carbon('1900-01-01'); - // $latest = $category->transactionjournals() - // ->orderBy('transaction_journals.date', 'DESC') - // ->orderBy('transaction_journals.order', 'ASC') - // ->orderBy('transaction_journals.id', 'DESC') - // ->first(); - // if ($latest) { - // $first = $latest->date; - // } - // - // // could also be a transaction, nowadays: - // $latestTransaction = $category->transactions() - // ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - // ->orderBy('transaction_journals.date', 'DESC') - // ->orderBy('transaction_journals.order', 'ASC') - // ->orderBy('transaction_journals.id', 'DESC') - // ->first(['transactions.*', 'transaction_journals.date']); - // if ($latestTransaction) { - // $second = new Carbon($latestTransaction->date); - // } - // if ($first > $second) { - // return $first; - // } - // - // return $second; - // } - - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * Where yyyy-mm-dd is the date and is the money spent using DEPOSITS in the $category - // * from all the users accounts. - // * - // * @param Category $category - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return array - // */ - // public function spentPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array - // { - // /** @var Collection $query */ - // $query = $category->transactionjournals() - // ->expanded() - // ->transactionTypes([TransactionType::WITHDRAWAL]) - // ->before($end) - // ->after($start) - // ->groupBy('transaction_journals.date'); - // $query->leftJoin( - // 'transactions as source', function (JoinClause $join) { - // $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); - // } - // ); - // - // if ($accounts->count() > 0) { - // $ids = $accounts->pluck('id')->toArray(); - // $query->whereIn('source.account_id', $ids); - // } - // - // $result = $query->get(['transaction_journals.date as dateFormatted', DB::raw('SUM(`source`.`amount`) AS `sum`')]); - // - // $return = []; - // foreach ($result->toArray() as $entry) { - // $return[$entry['dateFormatted']] = $entry['sum']; - // } - // - // return $return; - // } /** * @param Category $category @@ -731,20 +250,25 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $accounts + * @param array $types * @param Carbon $start * @param Carbon $end * * @return Collection */ - public function journalsInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : Collection + public function journalsInPeriodWithoutCategory(Collection $accounts, array $types, Carbon $start, Carbon $end) : Collection { /** @var Collection $set */ $query = $this->user - ->transactionjournals() - ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('category_transaction_journal.id') - ->before($end) - ->after($start); + ->transactionjournals(); + if (count($types) > 0) { + $query->transactionTypes($types); + } + + $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('category_transaction_journal.id') + ->before($end) + ->after($start); if ($accounts->count() > 0) { $accountIds = $accounts->pluck('id')->toArray(); @@ -777,7 +301,9 @@ class CategoryRepository implements CategoryRepositoryInterface // this second set REALLY doesn't have any categories. $secondSet = $secondQuery->get(['transactions.transaction_journal_id']); $allIds = $secondSet->pluck('transaction_journal_id')->toArray(); - $return = $this->user->transactionjournals()->sortCorrectly()->expanded()->whereIn('transaction_journals.id', $allIds)->get(TransactionJournal::queryFields()); + $return = $this->user->transactionjournals()->sortCorrectly()->expanded()->whereIn('transaction_journals.id', $allIds)->get( + TransactionJournal::queryFields() + ); return $return; @@ -859,7 +385,8 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string { - $journals = $this->journalsInPeriodWithoutCategory($accounts, $start, $end); + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $journals = $this->journalsInPeriodWithoutCategory($accounts, $types, $start, $end); $sum = '0'; foreach ($journals as $journal) { $sum = bcadd(TransactionJournal::amount($journal), $sum); @@ -900,4 +427,23 @@ class CategoryRepository implements CategoryRepositoryInterface return $category; } + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string + { + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $journals = $this->journalsInPeriodWithoutCategory($accounts, $types, $start, $end); + $sum = '0'; + foreach ($journals as $journal) { + $sum = bcadd(TransactionJournal::amount($journal), $sum); + } + + return $sum; + } } diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index b561dd2060..71f5f42998 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -16,20 +16,6 @@ use Illuminate\Support\Collection; interface CategoryRepositoryInterface { - - // /** - // * Returns a collection of Categories appended with the amount of money that has been earned - // * in these categories, based on the $accounts involved, in period X, grouped per month. - // * The amount earned in category X in period X is saved in field "earned". - // * - // * @param $accounts - // * @param $start - // * @param $end - // * - // * @return Collection - // */ - // public function earnedForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection; - /** * @param Category $category * @@ -37,80 +23,6 @@ interface CategoryRepositoryInterface */ public function destroy(Category $category): bool; - // /** - // * This method returns a very special collection for each category: - // * - // * category, year, expense/earned, amount - // * - // * categories can be duplicated. - // * - // * @param Collection $categories - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function listMultiYear(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection; - - // /** - // * Returns a list of transaction journals in the range (all types, all accounts) that have no category - // * associated to them. - // * - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function listNoCategory(Carbon $start, Carbon $end): Collection; - - // /** - // * Returns a collection of Categories appended with the amount of money that has been spent - // * in these categories, based on the $accounts involved, in period X, grouped per month. - // * The amount earned in category X in period X is saved in field "spent". - // * - // * @param $accounts - // * @param $start - // * @param $end - // * - // * @return Collection - // */ - // public function spentForAccountsPerMonth(Collection $accounts, Carbon $start, Carbon $end): Collection; - - // /** - // * Returns the total amount of money related to transactions without any category connected to - // * it. Returns either the earned amount. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return string - // */ - // public function sumEarnedNoCategory(Collection $accounts, Carbon $start, Carbon $end): string; - - // /** - // * Returns the total amount of money related to transactions without any category connected to - // * it. Returns either the spent amount. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return string - // */ - // public function sumSpentNoCategory(Collection $accounts, Carbon $start, Carbon $end): string; - - - // /** - // * @param Category $category - // * @param Carbon|null $start - // * @param Carbon|null $end - // * - // * @return int - // */ - // public function countJournals(Category $category, Carbon $start = null, Carbon $end = null): int; - /** * @param Collection $categories * @param Collection $accounts @@ -121,22 +33,14 @@ interface CategoryRepositoryInterface */ public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string; - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * Where yyyy-mm-dd is the date and is the money earned using DEPOSITS in the $category - // * from all the users accounts. - // * - // * @param Category $category - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return array - // */ - // public function earnedPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array; + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string; /** * Find a category @@ -147,12 +51,6 @@ interface CategoryRepositoryInterface */ public function find(int $categoryId) : Category; - // /** - // * @param Category $category - // * - // * @return Carbon - // */ - /** * @param Category $category * @param Collection $accounts @@ -199,12 +97,13 @@ interface CategoryRepositoryInterface /** * @param Collection $accounts + * @param array $types * @param Carbon $start * @param Carbon $end * * @return Collection */ - public function journalsInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : Collection; + public function journalsInPeriodWithoutCategory(Collection $accounts, array $types, Carbon $start, Carbon $end) : Collection; /** * Return most recent transaction(journal) date. @@ -225,61 +124,6 @@ interface CategoryRepositoryInterface * @return string */ public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string; - // /** - // * @param Category $category - // * @param int $page - // * @param int $pageSize - // * - // * @return Collection - // */ - // public function getJournals(Category $category, int $page, int $pageSize = 50): Collection; - - // /** - // * @param Category $category - // * @param Collection $accounts - // * - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getJournalsForAccountsInRange(Category $category, Collection $accounts, Carbon $start, Carbon $end): Collection; - - // /** - // * @param Category $category - // * @param Carbon $start - // * @param Carbon $end - // * @param int $page - // * @param int $pageSize - // * - // * - // * @return Collection - // */ - // public function getJournalsInRange(Category $category, Carbon $start, Carbon $end, int $page, int $pageSize = 50): Collection; - - // /** - // * @param Category $category - // * - // * @return Carbon - // */ - // public function getLatestActivity(Category $category): Carbon; - - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $category - // * from all the users accounts. - // * - // * @param Category $category - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return array - // */ - // public function spentPerDay(Category $category, Carbon $start, Carbon $end, Collection $accounts): array; /** * @param array $data diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js index 521989587f..22d6797f08 100644 --- a/public/js/ff/reports/default/year.js +++ b/public/js/ff/reports/default/year.js @@ -36,11 +36,6 @@ function drawChart() { columnChart('chart/category/period/' + categoryId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, id); }); - - //stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets'); - stackedColumnChart('chart/category/spent-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-period'); - stackedColumnChart('chart/category/earned-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-earned-in-period'); - } From 529bf50c85742972e3a6f556116086fa0fad0e42 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 10:37:56 +0200 Subject: [PATCH 101/206] Removed some dead code. --- .../Controllers/Chart/CategoryController.php | 87 ------------------- 1 file changed, 87 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index b6ba76a36c..15bd8c0405 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -148,7 +148,6 @@ class CategoryController extends Controller } /** - * @param $reportType * @param Carbon $start * @param Carbon $end * @param Collection $accounts @@ -292,72 +291,6 @@ class CategoryController extends Controller return Response::json($data); } - /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $set - * @param Collection $categories - * - * @return Collection - */ - private function filterCollection(Carbon $start, Carbon $end, Collection $set, Collection $categories): Collection - { - /** - * $entries = new Collection; - * - * while ($start < $end) { // filter the set: - * $row = [clone $start]; - * $currentSet = $set->filter( // get possibly relevant entries from the big $set - * function (Category $category) use ($start) { - * return $category->dateFormatted == $start->format('Y-m'); - * } - * ); - * /** @var Category $category - * foreach ($categories as $category) { // check for each category if its in the current set. - * $entry = $currentSet->filter( // if its in there, use the value. - * function (Category $cat) use ($category) { - * return ($cat->id == $category->id); - * } - * )->first(); - * if (!is_null($entry)) { - * $row[] = $entry->earned ? round($entry->earned, 2) : round($entry->spent, 2); - * } else { - * $row[] = 0; - * } - * } - * $entries->push($row); - * $start->addMonth(); - * } - * - * return $entries; - * */ - } - - /** - * Not the most elegant solution but it works. - * - * @param Collection $entries - * - * @return Collection - */ - private function invertSelection(Collection $entries): Collection - { - /** - * $result = new Collection; - * foreach ($entries as $entry) { - * $new = [$entry[0]]; - * $count = count($entry); - * for ($i = 1; $i < $count; $i++) { - * $new[$i] = ($entry[$i] * -1); - * } - * $result->push($new); - * } - * - * return $result; - * **/ - - } - /** * @param CRI $repository * @param Category $category @@ -396,26 +329,6 @@ class CategoryController extends Controller return $data; - /** - * // get amount earned in period, grouped by day. - * // get amount spent in period, grouped by day. - * $spentArray = $repository->spentPerDay($category, $start, $end, new Collection); - * $earnedArray = $repository->earnedPerDay($category, $start, $end, new Collection); - * - * while ($start <= $end) { - * $str = $start->format('Y-m-d'); - * $spent = $spentArray[$str] ?? '0'; - * $earned = $earnedArray[$str] ?? '0'; - * $date = Navigation::periodShow($start, '1D'); - * $entries->push([clone $start, $date, $spent, $earned]); - * $start->addDay(); - * } - * - * $data = $this->generator->period($entries); - * $cache->store($data); - * - * return $data; - */ } } From 037d84b810cdb7df588c71df3632b02db67120a8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 17:17:43 +0200 Subject: [PATCH 102/206] Fixes for transactions. --- app/Http/Controllers/CategoryController.php | 1 - .../Controllers/Chart/BudgetController.php | 4 +- .../Controllers/Chart/CategoryController.php | 13 +- app/Repositories/Budget/BudgetRepository.php | 111 +++++++---- .../Category/CategoryRepository.php | 179 ++++++++++++++---- app/Support/Twig/Journal.php | 2 + 6 files changed, 228 insertions(+), 82 deletions(-) diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 696145827b..1a5895b2a3 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -162,7 +162,6 @@ class CategoryController extends Controller // list of ranges for list of periods: // oldest transaction in category: - //$start = $repository->getFirstActivityDate($category); $start = $repository->firstUseDate($category, new Collection); $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($start, $range); diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 162ca52394..ce5132628b 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -150,7 +150,7 @@ class BudgetController extends Controller $cache->addProperty('budget'); $cache->addProperty('all'); if ($cache->has()) { - return Response::json($cache->get()); + //return Response::json($cache->get()); } $budgets = $repository->getActiveBudgets(); $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); @@ -203,6 +203,8 @@ class BudgetController extends Controller $data = $this->generator->frontpage($allEntries); $cache->store($data); + return ' ' . json_encode($data); + return Response::json($data); } diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 15bd8c0405..5e432520fe 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -9,7 +9,6 @@ use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Log; @@ -148,10 +147,10 @@ class CategoryController extends Controller } /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * @param Collection $categories + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * @param Collection $categories * * @return \Illuminate\Http\JsonResponse */ @@ -241,8 +240,8 @@ class CategoryController extends Controller // return Response::json($cache->get()); } - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); + /** @var CRI $repository */ + $repository = app(CRI::class); $categoryCollection = new Collection([$category]); // loop over period, add by users range: $current = clone $start; diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 9e17c3c244..1d7997e4ba 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -15,7 +15,6 @@ use FireflyIII\User; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; -use Log; /** * Class BudgetRepository @@ -78,7 +77,7 @@ class BudgetRepository implements BudgetRepositoryInterface { $oldest = Carbon::create()->startOfYear(); $journal = $budget->transactionjournals()->orderBy('date', 'ASC')->first(); - if ($journal) { + if (!is_null($journal)) { $oldest = $journal->date < $oldest ? $journal->date : $oldest; } @@ -86,8 +85,9 @@ class BudgetRepository implements BudgetRepositoryInterface ->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id') ->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']); - if ($transaction) { - $oldest = $transaction->date < $oldest ? $transaction->date : $oldest; + if (!is_null($transaction)) { + $carbon = new Carbon($transaction->date); + $oldest = $carbon < $oldest ? $carbon : $oldest; } return $oldest; @@ -187,8 +187,8 @@ class BudgetRepository implements BudgetRepositoryInterface // first get all journals for all budget(s): $journalQuery = $this->user->transactionjournals() ->expanded() - ->before($end) ->sortCorrectly() + ->before($end) ->after($start) ->leftJoin( 'transactions as source', @@ -204,6 +204,7 @@ class BudgetRepository implements BudgetRepositoryInterface } // get them: $journals = $journalQuery->get(TransactionJournal::queryFields()); + //Log::debug('journalsInPeriod journal count is ' . $journals->count()); // then get transactions themselves. @@ -302,15 +303,80 @@ class BudgetRepository implements BudgetRepositoryInterface */ public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string { - $set = $this->journalsInPeriod($budgets, $accounts, $start, $end); - //Log::debug('spentInPeriod set count is ' . $set->count()); - $sum = '0'; - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $sum = bcadd($sum, TransactionJournal::amount($journal)); + // first collect actual transaction journals (fairly easy) + $query = $this->user + ->transactionjournals() + ->distinct() + ->leftJoin( + 'transactions as t', function (JoinClause $join) { + $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + } + ); + + if ($end >= $start) { + $query->before($end)->after($start); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('t.account_id', $accountIds); + } + if ($budgets->count() > 0) { + $budgetIds = $budgets->pluck('id')->toArray(); + $query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('budget_transaction_journal.budget_id', $budgetIds); } - Log::debug('spentInPeriod between ' . $start->format('Y-m-d') . ' and ' . $end->format('Y-m-d') . ' is ' . $sum); + // that should do it: + $first = strval($query->sum('t.amount')); + + // then collection transactions (harder) + $query = $this->user->transactions() + ->where('transactions.amount', '<', 0) + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')); + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('transactions.account_id', $accountIds); + } + if ($budgets->count() > 0) { + $budgetIds = $budgets->pluck('id')->toArray(); + $query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id'); + $query->whereIn('budget_transaction.budget_id', $budgetIds); + } + $second = strval($query->sum('transactions.amount')); + + return bcadd($first, $second); + } + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string + { + $query = $this->user->transactionjournals() + ->distinct() + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin( + 'transactions as t', function (JoinClause $join) { + $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + } + ) + ->leftJoin('budget_transaction', 't.id', '=', 'budget_transaction.transaction_id') + ->whereNull('budget_transaction_journal.id') + ->whereNull('budget_transaction.id') + ->before($end) + ->after($start); + + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + + $query->whereIn('t.account_id', $accountIds); + } + $sum = strval($query->sum('t.amount')); return $sum; } @@ -402,25 +468,4 @@ class BudgetRepository implements BudgetRepositoryInterface return $limit; } - /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string - { - $set = $this->journalsInPeriodWithoutBudget($accounts, $start, $end); - //Log::debug('spentInPeriod set count is ' . $set->count()); - $sum = '0'; - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $sum = bcadd($sum, TransactionJournal::amount($journal)); - } - - Log::debug('spentInPeriodWithoutBudget between ' . $start->format('Y-m-d') . ' and ' . $end->format('Y-m-d') . ' is ' . $sum); - - return $sum; - } } diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 7c1eff2d91..c02a8ad1a1 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -8,8 +8,10 @@ use FireflyIII\Models\Category; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; +use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; +use Log; /** * Class CategoryRepository @@ -53,12 +55,24 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string { - $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; - $journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); - $sum = '0'; - foreach ($journals as $journal) { - $sum = bcadd(TransactionJournal::amount($journal), $sum); - } + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $sum = bcmul($this->sumInPeriod($categories, $accounts, $types, $start, $end), '-1'); + + return $sum; + + } + + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string + { + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $sum = $this->sumInPeriodWithoutCategory($accounts, $types, $start, $end); return $sum; } @@ -80,7 +94,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $category; } - /** * @param Category $category * @param Collection $accounts @@ -118,10 +131,13 @@ class CategoryRepository implements CategoryRepositoryInterface $firstTransactionQuery->whereIn('transactions.account_id', $ids); } - $firstTransaction = $firstJournalQuery->first(['transaction_journals.*']); + $firstTransaction = $firstTransactionQuery->first(['transaction_journals.*']); - if (!is_null($firstTransaction) && !is_null($first) && $firstTransaction->date < $first) { - $first = $firstTransaction->date; + if (!is_null($firstTransaction) && ((!is_null($first) && $firstTransaction->date < $first) || is_null($first))) { + $first = new Carbon($firstTransaction->date); + } + if (is_null($first)) { + return new Carbon('1900-01-01'); } return $first; @@ -162,25 +178,30 @@ class CategoryRepository implements CategoryRepositoryInterface $first = $query->get(TransactionJournal::queryFields()); // then collection transactions (harder) - $query = $this->user->transactions(); - $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); - $query->where('category_transaction.category_id', $category->id); + $query = $this->user->transactionjournals()->distinct() + ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id') + ->where('category_transaction.category_id', $category->id); $second = $query->get(['transaction_journals.*']); - $complete = $complete->merge($first); $complete = $complete->merge($second); // sort: + /** @var Collection $complete */ $complete = $complete->sortByDesc( - function (TransactionJournal $journal) { - return $journal->date->format('Ymd'); + function ($model) { + $date = new Carbon($model->date); + + return intval($date->format('U')); } ); - // create paginator - $offset = ($page - 1) * $pageSize; - $subSet = $complete->slice($offset, $pageSize); + $offset = ($page - 1) * $pageSize; + Log::debug('Page is ' . $page); + Log::debug('Offset is ' . $offset); + Log::debug('pagesize is ' . $pageSize); + $subSet = $complete->slice($offset, $pageSize)->all(); $paginator = new LengthAwarePaginator($subSet, $complete->count(), $pageSize, $page); return $paginator; @@ -322,6 +343,7 @@ class CategoryRepository implements CategoryRepositoryInterface /** @var TransactionJournal $first */ $lastJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC'); + Log::debug('lastUseDate ' . $category->name . ' (' . $category->id . ')'); if ($accounts->count() > 0) { // filter journals: @@ -334,6 +356,7 @@ class CategoryRepository implements CategoryRepositoryInterface if ($lastJournal) { $last = $lastJournal->date; + Log::debug('last is now ' . $last); } // check transactions: @@ -347,10 +370,15 @@ class CategoryRepository implements CategoryRepositoryInterface $lastTransactionQuery->whereIn('transactions.account_id', $ids); } - $lastTransaction = $lastJournalQuery->first(['transaction_journals.*']); + $lastTransaction = $lastTransactionQuery->first(['transaction_journals.*']); + if (!is_null($lastTransaction)) { + } + if (!is_null($lastTransaction) && ((!is_null($last) && $lastTransaction->date < $last) || is_null($last))) { + $last = new Carbon($lastTransaction->date); + } - if (!is_null($lastTransaction) && !is_null($last) && $lastTransaction->date < $last) { - $last = $lastTransaction->date; + if (is_null($last)) { + return new Carbon('1900-01-01'); } return $last; @@ -366,12 +394,8 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string { - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - $journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); - $sum = '0'; - foreach ($journals as $journal) { - $sum = bcadd(TransactionJournal::amount($journal), $sum); - } + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $sum = $this->sumInPeriod($categories, $accounts, $types, $start, $end); return $sum; } @@ -385,12 +409,8 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string { - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - $journals = $this->journalsInPeriodWithoutCategory($accounts, $types, $start, $end); - $sum = '0'; - foreach ($journals as $journal) { - $sum = bcadd(TransactionJournal::amount($journal), $sum); - } + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $sum = $this->sumInPeriodWithoutCategory($accounts, $types, $start, $end); return $sum; } @@ -429,21 +449,100 @@ class CategoryRepository implements CategoryRepositoryInterface } /** + * @param Collection $categories * @param Collection $accounts + * @param array $types * @param Carbon $start * @param Carbon $end * * @return string */ - public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string + private function sumInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): string { - $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; - $journals = $this->journalsInPeriodWithoutCategory($accounts, $types, $start, $end); - $sum = '0'; - foreach ($journals as $journal) { - $sum = bcadd(TransactionJournal::amount($journal), $sum); + // first collect actual transaction journals (fairly easy) + $query = $this->user + ->transactionjournals() + ->distinct() + ->transactionTypes($types) + ->leftJoin( + 'transactions as t', function (JoinClause $join) { + $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + } + ); + + if ($end >= $start) { + $query->before($end)->after($start); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('t.account_id', $accountIds); + } + if ($categories->count() > 0) { + $categoryIds = $categories->pluck('id')->toArray(); + $query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('category_transaction_journal.category_id', $categoryIds); } + // that should do it: + $first = strval($query->sum('t.amount')); + + // then collection transactions (harder) + $query = $this->user->transactions() + ->where('transactions.amount', '<', 0) + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')); + if (count($types) > 0) { + $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); + $query->whereIn('transaction_types.type', $types); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('transactions.account_id', $accountIds); + } + if ($categories->count() > 0) { + $categoryIds = $categories->pluck('id')->toArray(); + $query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); + $query->whereIn('category_transaction.category_id', $categoryIds); + } + $second = strval($query->sum('transactions.amount')); + + return bcadd($first, $second); + + } + + /** + * @param Collection $accounts + * @param array $types + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + private function sumInPeriodWithoutCategory(Collection $accounts, array $types, Carbon $start, Carbon $end): string + { + $query = $this->user->transactionjournals() + ->distinct() + ->transactionTypes($types) + ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin( + 'transactions as t', function (JoinClause $join) { + $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + } + ) + ->leftJoin('category_transaction', 't.id', '=', 'category_transaction.transaction_id') + ->whereNull('category_transaction_journal.id') + ->whereNull('category_transaction.id') + ->before($end) + ->after($start); + + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + + $query->whereIn('t.account_id', $accountIds); + } + $sum = strval($query->sum('t.amount')); + return $sum; + } } diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 18fd9ab2bc..ff505f0e12 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -43,6 +43,7 @@ class Journal extends Twig_Extension } $array[] = '' . e($entry->name) . ''; } + $array = array_unique($array); $result = join(', ', $array); $cache->store($result); @@ -111,6 +112,7 @@ class Journal extends Twig_Extension } $array[] = '' . e($entry->name) . ''; } + $array = array_unique($array); $result = join(', ', $array); $cache->store($result); From 7c39a04c4ba2ca1136d1ffbae4ac2dc3a3c564e9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 17:33:22 +0200 Subject: [PATCH 103/206] Cleanup. --- app/Models/TransactionJournal.php | 7 +---- .../Models/TransactionJournalSupport.php | 27 +------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 131b558f2d..2557b67407 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -341,11 +341,6 @@ class TransactionJournal extends TransactionJournalSupport $query->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id'); // left join destination (for amount and account info). - $query->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); - } - ); $query->groupBy('transaction_journals.id'); $query->with(['categories', 'budgets', 'attachments', 'bill', 'transactions']); } @@ -362,7 +357,7 @@ class TransactionJournal extends TransactionJournalSupport $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); } $query->whereIn('transaction_types.type', $types); - } + } /** * diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index e7aab0f43f..53e1c2c83d 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -12,7 +12,6 @@ namespace FireflyIII\Support\Models; use Carbon\Carbon; -use DB; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; @@ -46,30 +45,8 @@ class TransactionJournalSupport extends Model return $cache->get(); } - if ($journal->isWithdrawal() && !is_null($journal->source_amount)) { - $cache->store($journal->source_amount); - - return $journal->source_amount; - } - if ($journal->isDeposit() && !is_null($journal->destination_amount)) { - $cache->store($journal->destination_amount); - - return $journal->destination_amount; - } - // saves on queries: - $amount = '0'; - if (count($journal->transactions) === 0) { - $amount = '0'; - foreach ($journal->transactions as $t) { - if ($t->amount > 0) { - $amount = bcadd($amount, $t->amount); - } - } - } - if ($amount === '0') { - $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount'); - } + $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount'); if ($journal->isWithdrawal()) { $amount = $amount * -1; @@ -275,8 +252,6 @@ class TransactionJournalSupport extends Model 'transaction_journals.*', 'transaction_types.type AS transaction_type_type', // the other field is called "transaction_type_id" so this is pretty consistent. 'transaction_currencies.code AS transaction_currency_code', - DB::raw('SUM(`transactions`.`amount`) as `amount`'), - DB::raw('(COUNT(`transactions`.`id`) * 2) as `count_transactions`'), ]; } From b7c446f7dbd140760460993e03361f1d38cf1e96 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 11 May 2016 23:03:13 +0200 Subject: [PATCH 104/206] Start with edit and view screens. --- app/Http/Controllers/HomeController.php | 12 +- .../Transaction/SplitController.php | 11 +- .../Controllers/TransactionController.php | 25 ++- resources/views/transactions/show.twig | 145 ++++++++++++------ 4 files changed, 124 insertions(+), 69 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index ded94b2943..0aafa57c49 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -153,10 +153,10 @@ class HomeController extends Controller { // these routes are not relevant for the help pages: $ignore = [ - 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview', - 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download', - 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down', - 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar', +// 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview', +// 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download', +// 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down', +// 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar', ]; $routes = Route::getRoutes(); /** @var \Illuminate\Routing\Route $route */ @@ -166,9 +166,7 @@ class HomeController extends Controller $methods = $route->getMethods(); if (!is_null($name) && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) { - foreach (array_keys(config('firefly.languages')) as $lang) { - echo 'touch ' . $lang . '/' . $name . '.md
'; - } + echo $name . '
'; } } diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 62870495d3..53edce6490 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -15,6 +15,7 @@ use FireflyIII\Crud\Split\JournalInterface; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SplitJournalFormRequest; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; @@ -41,6 +42,13 @@ class SplitController extends Controller View::share('title', trans('firefly.split-transactions')); } + public function edit(TransactionJournal $journal) + { + $count = $journal->transactions()->count(); + if ($count === 2) { + return redirect(route('transactions.edit', [$journal->id])); + } + } /** * @param Request $request @@ -88,7 +96,8 @@ class SplitController extends Controller * @param SplitJournalFormRequest $request * @param JournalInterface $repository * - * @return mixed + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws FireflyException */ public function postJournalFromStore(SplitJournalFormRequest $request, JournalInterface $repository) { diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 71871aabaa..a03d971893 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -12,6 +12,7 @@ namespace FireflyIII\Http\Controllers; use Amount; use Auth; use Carbon\Carbon; +use DB; use ExpandedForm; use FireflyIII\Events\TransactionJournalStored; use FireflyIII\Events\TransactionJournalUpdated; @@ -21,7 +22,6 @@ use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; @@ -147,6 +147,10 @@ class TransactionController extends Controller */ public function edit(TransactionJournal $journal) { + $count = $journal->transactions()->count(); + if ($count > 2) { + return redirect(route('split.journal.edit', [$journal->id])); + } /** @var ARI $accountRepository */ $accountRepository = app(ARI::class); /** @var BudgetRepositoryInterface $budgetRepository */ @@ -400,12 +404,11 @@ class TransactionController extends Controller } /** - * @param JournalRepositoryInterface $repository - * @param TransactionJournal $journal + * @param TransactionJournal $journal * * @return \Illuminate\View\View */ - public function show(JournalRepositoryInterface $repository, TransactionJournal $journal) + public function show(TransactionJournal $journal) { /** @var Collection $set */ @@ -415,17 +418,13 @@ class TransactionController extends Controller $event->piggyBank = $event->piggyBank()->withTrashed()->first(); } ); - - $journal->transactions->each( - function (Transaction $t) use ($journal, $repository) { - $t->before = $repository->getAmountBefore($journal, $t); - $t->after = bcadd($t->before, $t->amount); - } + $transactions = $journal->transactions()->groupBy('transactions.account_id')->orderBy('amount', 'ASC')->get( + ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] ); - $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); - $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; - return view('transactions.show', compact('journal', 'events', 'subTitle', 'what')); + return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions')); } /** diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index c139094dde..610d21acff 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -132,7 +132,7 @@ {% endif %} - + {% endfor %} @@ -154,54 +154,103 @@ {% endif %}
- - {% for t in journal.transactions %} -
-
-

{{ t.account.name }}

+ {% if transactions.count == 2 %} + {% for t in transactions %} +
+
+

{{ t.account.name }}

+
+ + + + + + + + + + + + + + + + + + {% if t.description %} + + + + + {% endif %} + {% if t.categories[0] %} + + + + + {% endif %} + {% if t.budgets[0] %} + + + + + {% endif %} +
{{ 'account'|_ }}{{ t.account.name }}
{{ 'account_type'|_ }}{{ t.account.accounttype.type|_ }}
{{ 'amount'|_ }}{{ t|formatTransaction }}
{{ 'newBalance'|_ }}null
{{ trans('form.description') }}{{ t.description }}
{{ 'category'|_ }} + {{ t.categories[0].name }} +
{{ 'budget'|_ }} + {{ t.budgets[0].name }} +
- - - - - - - - - - - - - - - - - - {% if t.description %} - - - - - {% endif %} - {% if t.categories[0] %} - - - - - {% endif %} - {% if t.budgets[0] %} - - - - - {% endif %} -
{{ 'account'|_ }}{{ t.account.name }}
{{ 'account_type'|_ }}{{ t.account.accounttype.type|_ }}
{{ 'amount'|_ }}{{ t|formatTransaction }}
{{ 'newBalance'|_ }}{{ t.before|formatAmount }} → {{ t.after|formatAmount }}
{{ trans('form.description') }}{{ t.description }}
{{ 'category'|_ }} - {{ t.categories[0].name }} -
{{ 'budget'|_ }} - {{ t.budgets[0].name }} -
-
- {% endfor %} + {% endfor %} + {% endif %}
+ + + {% if transactions.count > 2 %} +
+
+ +
+
+

Transactions

+
+ + + + + + + + + + + + + + {% for t in transactions %} + + + + + + + + + {% endfor %} + +
{{ trans('list.account') }}{{ trans('list.account_type') }}{{ trans('list.amount') }}{{ trans('list.new_balance') }}{{ trans('list.description') }}{{ trans('list.category') }}{{ trans('list.budget') }}
{{ t.account.name }}{{ t.account.accounttype.type|_ }}{{ t.sum|formatAmount }}null{{ t.description }} + {% if t.categories[0] %} + {{ t.categories[0].name }} + {% endif %} + + {% if t.budgets[0] %} + {{ t.budgets[0].name }} + {% endif %} +
+
+
+
+ {% endif %} + {% endblock %} From ce7eebac5cca61b54315f01d081447360730f00e Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 12 May 2016 10:38:44 +0200 Subject: [PATCH 105/206] Build edit split transactions. --- .../Transaction/SplitController.php | 29 +++++++++++++++++++ .../Controllers/TransactionController.php | 2 ++ app/Http/routes.php | 3 +- .../Account/AccountRepository.php | 14 +++++++-- .../Models/TransactionJournalSupport.php | 11 +++---- 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 53edce6490..b14581509a 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -48,6 +48,35 @@ class SplitController extends Controller if ($count === 2) { return redirect(route('transactions.edit', [$journal->id])); } + + /** @var CurrencyRepositoryInterface $currencyRepository */ + $currencyRepository = app(CurrencyRepositoryInterface::class); + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + + /** @var BudgetRepositoryInterface $budgetRepository */ + $budgetRepository = app(BudgetRepositoryInterface::class); + + /** @var PiggyBankRepositoryInterface $piggyBankRepository */ + $piggyBankRepository = app(PiggyBankRepositoryInterface::class); + + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyBankRepository->getPiggyBanks()); + $amount = TransactionJournal::amountPositive($journal); + + // get source account: + $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $destinationAccounts = TransactionJournal::destinationAccountList($journal); + + // get the transactions: + + return view( + 'split.journals.edit', + compact('currencies', 'amount', 'piggyBanks', 'sourceAccounts', 'destinationAccounts', 'assetAccounts', 'budgets', 'what', 'journal') + ); } /** diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index a03d971893..ec3f07f866 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -418,6 +418,8 @@ class TransactionController extends Controller $event->piggyBank = $event->piggyBank()->withTrashed()->first(); } ); + + // TODO different for each transaction type! $transactions = $journal->transactions()->groupBy('transactions.account_id')->orderBy('amount', 'ASC')->get( ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] ); diff --git a/app/Http/routes.php b/app/Http/routes.php index aea15c53ab..aa141631e6 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -343,7 +343,8 @@ Route::group( */ Route::get('/transaction/split', ['uses' => 'Transaction\SplitController@journalFromStore', 'as' => 'split.journal.from-store']); Route::post('/transaction/split', ['uses' => 'Transaction\SplitController@postJournalFromStore', 'as' => 'split.journal.from-store.post']); - + Route::get('/transaction/edit-split/{journal}',['uses' => 'Transaction\SplitController@edit', 'as' => 'split.journal.edit']); + Route::post('/transaction/edit-split/{journal}',['uses' => 'Transaction\SplitController@update', 'as' => 'split.journal.update']); /** * Tag Controller */ diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 62212762c6..d0f8631b8a 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -182,8 +182,18 @@ class AccountRepository implements AccountRepositoryInterface ->expanded() ->sortCorrectly() ->before($end) - ->where('destination_account.id', $account->id) - ->whereIn('source_account.id', $ids) + ->leftJoin( + 'transactions as dest', function (JoinClause $join) { + $join->on('dest.transaction_journal_id', '=', 'transaction_journals.id')->where('dest.amount', '>', 0); + } + ) + ->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); + } + ) + ->where('dest.account_id', $account->id) + ->whereIn('source.account_id', $ids) ->after($start) ->get(TransactionJournal::queryFields()); diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index 53e1c2c83d..bc49cc3d1e 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -72,13 +72,10 @@ class TransactionJournalSupport extends Model return $cache->get(); } - $amount = '0'; - /** @var Transaction $t */ - foreach ($journal->transactions as $t) { - if ($t->amount > 0) { - $amount = $t->amount; - } - } + // saves on queries: + $amount = $journal->transactions()->where('amount', '>', 0)->get()->sum('amount'); + + $amount = strval($amount); $cache->store($amount); return $amount; From 988049061de65cadf5c61a31d9b41795a1c14668 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 12 May 2016 12:13:10 +0200 Subject: [PATCH 106/206] Start with edit split journals routine. --- resources/views/split/journals/edit.twig | 196 +++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 resources/views/split/journals/edit.twig diff --git a/resources/views/split/journals/edit.twig b/resources/views/split/journals/edit.twig new file mode 100644 index 0000000000..e62198f22c --- /dev/null +++ b/resources/views/split/journals/edit.twig @@ -0,0 +1,196 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists }} +{% endblock %} +{% block content %} + {{ Form.model(journal, {'class' : 'form-horizontal','id' : 'update','url' : route('split.journal.update',journal.id) } ) }} + + + + + + {% if errors.all()|length > 0 %} +
+
+
+
+

{{ 'errors'|_ }}

+
+ +
+
+
+
    + {% for key, err in errors.all() %} +
  • {{ err }}
  • + {% endfor %} +
+
+
+
+
+ {% endif %} +
+
+
+
+

{{ 'transaction_meta_data'|_ }}

+ +
+ +
+
+
+ {{ ExpandedForm.text('journal_description') }} + {{ ExpandedForm.select('journal_currency_id', currencies, journal.transaction_currency_id) }} + {{ ExpandedForm.staticText('journal_amount', amount|formatAmount ) }} + + + {% if what == 'withdrawal' or what == 'transfer' %} + {{ ExpandedForm.staticText('journal_asset_source_account', assetAccounts[sourceAccounts.first.id]) }} + + {% endif %} + + {% if what == 'deposit' %} + {{ ExpandedForm.staticText('revenue_account', sourceAccounts.first.name) }} + + {% endif %} + + {% if what == 'transfer' %} + {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[sourceAccounts.first.id]) }} + + {% endif %} +
+
+
+
+
+
+

{{ 'transaction_dates'|_ }}

+ +
+ +
+
+
+ {{ ExpandedForm.date('date', journal.date) }} + + {{ ExpandedForm.date('interest_date', journal.interest_date) }} + + {{ ExpandedForm.date('book_date', journal.book_date) }} + + {{ ExpandedForm.date('process_date', journal.process_date) }} +
+
+
+
+
+
+
+
+

{{ 'splits'|_ }}

+ +
+ +
+
+
+ + + + + + + + + + {% if data.what == 'withdrawal' or data.what == 'deposit' %} + + {% endif %} + + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'transfer' %} + + {% endif %} + + + + {% for index, descr in data.description %} + + + + + + {% if data.what == 'withdrawal' %} + + {% endif %} + + + {% if data.what == 'deposit' %} + + {% endif %} + + + + {% if data.what == 'withdrawal' %} + + {% endif %} + + {% if data.what == 'transfer' %} + + {% endif %} + + {% endfor %} + +
{{ trans('list.split_number') }}{{ trans('list.description') }}{{ trans('list.destination') }}{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}{{ trans('list.piggy_bank') }}
#{{ loop.index }} + + + + + {{ Form.select('destination_account_id[]', assetAccounts, data.destination_account_id[index], {class: 'form-control'}) }} + + + + {{ Form.select('budget_id[]', budgets, data.budget_id[index], {class: 'form-control'}) }} + + + + {{ Form.select('piggy_bank_id[]', piggyBanks, data.piggy_bank_id[index], {class: 'form-control'}) }} +
+

+
+ {{ 'add_another_split'|_ }} +

+

+ +

+
+
+
+
+ + +{% endblock %} +{% block scripts %} + + + + +{% endblock %} +{% block styles %} +{% endblock %} From cfcc4ce88a128cdfd057f8f559b0afd4859e9b63 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 12 May 2016 16:34:44 +0200 Subject: [PATCH 107/206] Let's leave this on false. --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 59778fd8b8..df8630b39b 100755 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ APP_ENV=production APP_DEBUG=false -APP_FORCE_SSL=true +APP_FORCE_SSL=false APP_KEY=SomeRandomStringOf32CharsExactly LOG_LEVEL=warning From d7ab482ae126a76ebec88419167aec31f597f065 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 12 May 2016 22:44:31 +0200 Subject: [PATCH 108/206] Various updates for split transactions. --- .../Transaction/SplitController.php | 101 ++++++-- app/Http/Requests/SplitJournalFormRequest.php | 1 + database/seeds/SplitDataSeeder.php | 5 +- public/js/ff/transactions/create-edit.js | 7 + resources/lang/en_US/firefly.php | 1 + resources/lang/en_US/form.php | 245 +++++++++--------- resources/lang/en_US/list.php | 3 + resources/views/split/journals/edit.twig | 55 ++-- .../views/split/journals/from-store.twig | 90 +++---- 9 files changed, 285 insertions(+), 223 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index b14581509a..27ed8387d2 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -15,11 +15,11 @@ use FireflyIII\Crud\Split\JournalInterface; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SplitJournalFormRequest; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Http\Request; use Log; use Session; @@ -42,7 +42,13 @@ class SplitController extends Controller View::share('title', trans('firefly.split-transactions')); } - public function edit(TransactionJournal $journal) + /** + * @param Request $request + * @param TransactionJournal $journal + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View + */ + public function edit(Request $request, TransactionJournal $journal) { $count = $journal->transactions()->count(); if ($count === 2) { @@ -57,25 +63,16 @@ class SplitController extends Controller /** @var BudgetRepositoryInterface $budgetRepository */ $budgetRepository = app(BudgetRepositoryInterface::class); - /** @var PiggyBankRepositoryInterface $piggyBankRepository */ - $piggyBankRepository = app(PiggyBankRepositoryInterface::class); - - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyBankRepository->getPiggyBanks()); - $amount = TransactionJournal::amountPositive($journal); - - // get source account: - $sourceAccounts = TransactionJournal::sourceAccountList($journal); - $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $preFilled = $this->arrayFromJournal($request, $journal); // get the transactions: return view( 'split.journals.edit', - compact('currencies', 'amount', 'piggyBanks', 'sourceAccounts', 'destinationAccounts', 'assetAccounts', 'budgets', 'what', 'journal') + compact('currencies', 'preFilled', 'amount', 'sourceAccounts', 'destinationAccounts', 'assetAccounts', 'budgets', 'what', 'journal') ); } @@ -104,19 +101,12 @@ class SplitController extends Controller /** @var BudgetRepositoryInterface $budgetRepository */ $budgetRepository = app(BudgetRepositoryInterface::class); - /** @var PiggyBankRepositoryInterface $piggyBankRepository */ - $piggyBankRepository = app(PiggyBankRepositoryInterface::class); - - $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyBankRepository->getPiggyBanks()); - - //Session::flash('warning', 'This feature is very experimental. Beware.'); - return view('split.journals.from-store', compact('currencies', 'piggyBanks', 'assetAccounts', 'budgets'))->with('data', $preFilled); + return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets', 'preFilled')); } @@ -154,6 +144,73 @@ class SplitController extends Controller return redirect(session('transactions.create.url')); } + /** + * @param SplitJournalFormRequest $request + * @param JournalInterface $repository + */ + public function update(SplitJournalFormRequest $request, JournalInterface $repository) + { + echo 'ok'; + } + + /** + * @param Request $request + * @param TransactionJournal $journal + * + * @return array + */ + private function arrayFromJournal(Request $request, TransactionJournal $journal): array + { + if (Session::has('_old_input')) { + Log::debug('Old input: ', session('_old_input')); + } + $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $firstSourceId = $sourceAccounts->first()->id; + $array = [ + 'journal_description' => $request->old('journal_description', $journal->description), + 'journal_amount' => TransactionJournal::amountPositive($journal), + 'sourceAccounts' => $sourceAccounts, + 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id), + 'destinationAccounts' => TransactionJournal::destinationAccountList($journal), + 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), + 'date' => $request->old('date', $journal->date), + 'interest_date' => $request->old('interest_date', $journal->interest_date), + 'book_date' => $request->old('book_date', $journal->book_date), + 'process_date' => $request->old('process_date', $journal->process_date), + 'description' => [], + 'destination_account_id' => [], + 'destination_account_name' => [], + 'amount' => [], + 'budget_id' => [], + 'category' => [], + ]; + $index = 0; + /** @var Transaction $transaction */ + foreach ($journal->transactions()->get() as $transaction) { + //if ($journal->isWithdrawal() && $transaction->account_id !== $firstSourceId) { + $array['description'][] = $transaction->description; + $array['destination_account_id'][] = $transaction->account_id; + $array['destination_account_name'][] = $transaction->account->name; + $array['amount'][] = $transaction->amount; + //} + $budget = $transaction->budgets()->first(); + $budgetId = 0; + if (!is_null($budget)) { + $budgetId = $budget->id; + } + + $category = $transaction->categories()->first(); + $categoryName = ''; + if (!is_null($category)) { + $categoryName = $category->name; + } + $array['budget_id'][] = $budgetId; + $array['category'][] = $categoryName; + } + + return $array; + } + /** * @param array $old * @@ -203,7 +260,6 @@ class SplitController extends Controller 'amount' => [], 'budget_id' => [], 'category' => [], - 'piggy_bank_id' => [], ]; // create the first transaction: @@ -213,7 +269,6 @@ class SplitController extends Controller $preFilled['amount'][] = $data['amount']; $preFilled['budget_id'][] = $data['budget_id']; $preFilled['category'][] = $data['category']; - $preFilled['piggy_bank_id'][] = $data['piggy_bank_id']; // echo '
';
         //        var_dump($data);
diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php
index 526fd30c45..142796293d 100644
--- a/app/Http/Requests/SplitJournalFormRequest.php
+++ b/app/Http/Requests/SplitJournalFormRequest.php
@@ -77,6 +77,7 @@ class SplitJournalFormRequest extends Request
         return [
             'what'                          => 'required|in:withdrawal,deposit,transfer',
             'journal_description'           => 'required|between:1,255',
+            'id'                            => 'numeric|belongsToUser:transaction_journals,id',
             'journal_source_account_id'     => 'numeric|belongsToUser:accounts,id',
             'journal_source_account_name.*' => 'between:1,255',
             'journal_currency_id'           => 'required|exists:transaction_currencies,id',
diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php
index a7c01b30db..826290d40b 100644
--- a/database/seeds/SplitDataSeeder.php
+++ b/database/seeds/SplitDataSeeder.php
@@ -225,6 +225,7 @@ class SplitDataSeeder extends Seeder
                     'account_id'             => $source->id,
                     'transaction_journal_id' => $journal->id,
                     'amount'                 => $amounts[$index] * -1,
+                    'description'            => 'Split Even Expense #' . $index,
 
                 ]
             );
@@ -234,6 +235,7 @@ class SplitDataSeeder extends Seeder
                     'account_id'             => $destination->id,
                     'transaction_journal_id' => $journal->id,
                     'amount'                 => $amounts[$index],
+                    'description'            => 'Split Even Expense #' . $index,
 
                 ]
             );
@@ -279,6 +281,7 @@ class SplitDataSeeder extends Seeder
                     'account_id'             => $source->id,
                     'transaction_journal_id' => $journal->id,
                     'amount'                 => $amounts[$index] * -1,
+                    'description'            => 'Split Uneven Expense #' . $index,
 
                 ]
             );
@@ -288,7 +291,7 @@ class SplitDataSeeder extends Seeder
                     'account_id'             => $destination->id,
                     'transaction_journal_id' => $journal->id,
                     'amount'                 => $amounts[$index],
-
+                    'description'            => 'Split Uneven Expense #' . $index,
                 ]
             );
 
diff --git a/public/js/ff/transactions/create-edit.js b/public/js/ff/transactions/create-edit.js
index 9bcf6b18f1..2561dbe90d 100644
--- a/public/js/ff/transactions/create-edit.js
+++ b/public/js/ff/transactions/create-edit.js
@@ -48,6 +48,13 @@ $(document).ready(function () {
             $('input[name="source_account_name[]"]').typeahead({source: data});
         });
     }
+    // and for split:
+    if ($('input[name="journal_source_account_name"]').length > 0) {
+        $.getJSON('json/revenue-accounts').done(function (data) {
+            $('input[name="journal_source_account_name"]').typeahead({source: data});
+        });
+    }
+
 
     if ($('input[name="description"]').length > 0 && what !== undefined) {
         $.getJSON('json/transaction-journals/' + what).done(function (data) {
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index 843459a908..7bd6d7fd79 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -811,6 +811,7 @@ return [
     'split_intro_three_withdrawal'              => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
     'split_table_intro_withdrawal'              => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
     'store_splitted_withdrawal'                 => 'Store splitted withdrawal',
+    'update_splitted_withdrawal'                 => 'Update splitted withdrawal',
 
     'split_title_deposit'       => 'Split your new deposit',
     'split_intro_one_deposit'   => 'Firefly supports the "splitting" of a deposit.',
diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
index f6ca7908bc..6e2c70cd48 100644
--- a/resources/lang/en_US/form.php
+++ b/resources/lang/en_US/form.php
@@ -10,125 +10,128 @@
 return [
 
     // new user:
-    'bank_name'                   => 'Bank name',
-    'bank_balance'                => 'Balance',
-    'savings_balance'             => 'Savings balance',
-    'credit_card_limit'           => 'Credit card limit',
-    'automatch'                   => 'Match automatically',
-    'skip'                        => 'Skip',
-    'name'                        => 'Name',
-    'active'                      => 'Active',
-    'amount_min'                  => 'Minimum amount',
-    'amount_max'                  => 'Maximum amount',
-    'match'                       => 'Matches on',
-    'repeat_freq'                 => 'Repeats',
-    'account_from_id'             => 'From account',
-    'account_to_id'               => 'To account',
-    'asset_destination_account'   => 'Asset account (destination)',
-    'asset_source_account'        => 'Asset account (source)',
-    'journal_description'         => 'Description',
-    'split_journal'               => 'Split this transaction',
-    'split_journal_explanation'   => 'Split this transaction in multiple parts',
-    'currency'                    => 'Currency',
-    'account_id'                  => 'Asset account',
-    'budget_id'                   => 'Budget',
-    'openingBalance'              => 'Opening balance',
-    'tagMode'                     => 'Tag mode',
-    'tagPosition'                 => 'Tag location',
-    'virtualBalance'              => 'Virtual balance',
-    'longitude_latitude'          => 'Location',
-    'targetamount'                => 'Target amount',
-    'accountRole'                 => 'Account role',
-    'openingBalanceDate'          => 'Opening balance date',
-    'ccType'                      => 'Credit card payment plan',
-    'ccMonthlyPaymentDate'        => 'Credit card monthly payment date',
-    'piggy_bank_id'               => 'Piggy bank',
-    'returnHere'                  => 'Return here',
-    'returnHereExplanation'       => 'After storing, return here to create another one.',
-    'returnHereUpdateExplanation' => 'After updating, return here.',
-    'description'                 => 'Description',
-    'expense_account'             => 'Expense account',
-    'revenue_account'             => 'Revenue account',
-    'amount'                      => 'Amount',
-    'date'                        => 'Date',
-    'interest_date'               => 'Interest date',
-    'book_date'                   => 'Book date',
-    'process_date'                => 'Processing date',
-    'category'                    => 'Category',
-    'tags'                        => 'Tags',
-    'deletePermanently'           => 'Delete permanently',
-    'cancel'                      => 'Cancel',
-    'targetdate'                  => 'Target date',
-    'tag'                         => 'Tag',
-    'under'                       => 'Under',
-    'symbol'                      => 'Symbol',
-    'code'                        => 'Code',
-    'iban'                        => 'IBAN',
-    'accountNumber'               => 'Account number',
-    'csv'                         => 'CSV file',
-    'has_headers'                 => 'Headers',
-    'date_format'                 => 'Date format',
-    'csv_config'                  => 'CSV import configuration',
-    'specifix'                    => 'Bank- or file specific fixes',
-    'csv_import_account'          => 'Default import account',
-    'csv_delimiter'               => 'CSV field delimiter',
-    'attachments[]'               => 'Attachments',
-    'store_new_withdrawal'        => 'Store new withdrawal',
-    'store_new_deposit'           => 'Store new deposit',
-    'store_new_transfer'          => 'Store new transfer',
-    'add_new_withdrawal'          => 'Add a new withdrawal',
-    'add_new_deposit'             => 'Add a new deposit',
-    'add_new_transfer'            => 'Add a new transfer',
-    'noPiggybank'                 => '(no piggy bank)',
-    'title'                       => 'Title',
-    'notes'                       => 'Notes',
-    'filename'                    => 'File name',
-    'mime'                        => 'Mime type',
-    'size'                        => 'Size',
-    'trigger'                     => 'Trigger',
-    'stop_processing'             => 'Stop processing',
-    'start_date'                  => 'Start of range',
-    'end_date'                    => 'End of range',
-    'export_start_range'          => 'Start of export range',
-    'export_end_range'            => 'End of export range',
-    'export_format'               => 'File format',
-    'include_attachments'         => 'Include uploaded attachments',
-    'include_config'              => 'Include configuration file',
-    'include_old_uploads'         => 'Include imported data',
-    'accounts'                    => 'Export transactions from these accounts',
-    'csv_comma'                   => 'A comma (,)',
-    'csv_semicolon'               => 'A semicolon (;)',
-    'csv_tab'                     => 'A tab (invisible)',
-    'delete_account'              => 'Delete account ":name"',
-    'delete_bill'                 => 'Delete bill ":name"',
-    'delete_budget'               => 'Delete budget ":name"',
-    'delete_category'             => 'Delete category ":name"',
-    'delete_currency'             => 'Delete currency ":name"',
-    'delete_journal'              => 'Delete transaction with description ":description"',
-    'delete_attachment'           => 'Delete attachment ":name"',
-    'delete_rule'                 => 'Delete rule ":title"',
-    'delete_rule_group'           => 'Delete rule group ":title"',
-    'attachment_areYouSure'       => 'Are you sure you want to delete the attachment named ":name"?',
-    'account_areYouSure'          => 'Are you sure you want to delete the account named ":name"?',
-    'bill_areYouSure'             => 'Are you sure you want to delete the bill named ":name"?',
-    'rule_areYouSure'             => 'Are you sure you want to delete the rule titled ":title"?',
-    'ruleGroup_areYouSure'        => 'Are you sure you want to delete the rule group titled ":title"?',
-    'budget_areYouSure'           => 'Are you sure you want to delete the budget named ":name"?',
-    'category_areYouSure'         => 'Are you sure you want to delete the category named ":name"?',
-    'currency_areYouSure'         => 'Are you sure you want to delete the currency named ":name"?',
-    'piggyBank_areYouSure'        => 'Are you sure you want to delete the piggy bank named ":name"?',
-    'journal_areYouSure'          => 'Are you sure you want to delete the transaction described ":description"?',
-    'mass_journal_are_you_sure'   => 'Are you sure you want to delete these transactions?',
-    'tag_areYouSure'              => 'Are you sure you want to delete the tag ":tag"?',
-    'permDeleteWarning'           => 'Deleting stuff from Firely is permanent and cannot be undone.',
-    'mass_make_selection'         => 'You can still prevent items from being deleted by removing the checkbox.',
-    'delete_all_permanently'      => 'Delete selected permanently',
-    'update_all_journals'         => 'Update these transactions',
-    'also_delete_transactions'    => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
-    'also_delete_rules'           => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
-    'also_delete_piggyBanks'      => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
-    'bill_keep_transactions'      => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
-    'budget_keep_transactions'    => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
-    'category_keep_transactions'  => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
-    'tag_keep_transactions'       => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+    'bank_name'                    => 'Bank name',
+    'bank_balance'                 => 'Balance',
+    'savings_balance'              => 'Savings balance',
+    'credit_card_limit'            => 'Credit card limit',
+    'automatch'                    => 'Match automatically',
+    'skip'                         => 'Skip',
+    'name'                         => 'Name',
+    'active'                       => 'Active',
+    'amount_min'                   => 'Minimum amount',
+    'amount_max'                   => 'Maximum amount',
+    'match'                        => 'Matches on',
+    'repeat_freq'                  => 'Repeats',
+    'journal_currency_id'          => 'Currency',
+    'journal_amount'               => 'Amount',
+    'journal_asset_source_account' => 'Asset account (source)',
+    'account_from_id'              => 'From account',
+    'account_to_id'                => 'To account',
+    'asset_destination_account'    => 'Asset account (destination)',
+    'asset_source_account'         => 'Asset account (source)',
+    'journal_description'          => 'Description',
+    'split_journal'                => 'Split this transaction',
+    'split_journal_explanation'    => 'Split this transaction in multiple parts',
+    'currency'                     => 'Currency',
+    'account_id'                   => 'Asset account',
+    'budget_id'                    => 'Budget',
+    'openingBalance'               => 'Opening balance',
+    'tagMode'                      => 'Tag mode',
+    'tagPosition'                  => 'Tag location',
+    'virtualBalance'               => 'Virtual balance',
+    'longitude_latitude'           => 'Location',
+    'targetamount'                 => 'Target amount',
+    'accountRole'                  => 'Account role',
+    'openingBalanceDate'           => 'Opening balance date',
+    'ccType'                       => 'Credit card payment plan',
+    'ccMonthlyPaymentDate'         => 'Credit card monthly payment date',
+    'piggy_bank_id'                => 'Piggy bank',
+    'returnHere'                   => 'Return here',
+    'returnHereExplanation'        => 'After storing, return here to create another one.',
+    'returnHereUpdateExplanation'  => 'After updating, return here.',
+    'description'                  => 'Description',
+    'expense_account'              => 'Expense account',
+    'revenue_account'              => 'Revenue account',
+    'amount'                       => 'Amount',
+    'date'                         => 'Date',
+    'interest_date'                => 'Interest date',
+    'book_date'                    => 'Book date',
+    'process_date'                 => 'Processing date',
+    'category'                     => 'Category',
+    'tags'                         => 'Tags',
+    'deletePermanently'            => 'Delete permanently',
+    'cancel'                       => 'Cancel',
+    'targetdate'                   => 'Target date',
+    'tag'                          => 'Tag',
+    'under'                        => 'Under',
+    'symbol'                       => 'Symbol',
+    'code'                         => 'Code',
+    'iban'                         => 'IBAN',
+    'accountNumber'                => 'Account number',
+    'csv'                          => 'CSV file',
+    'has_headers'                  => 'Headers',
+    'date_format'                  => 'Date format',
+    'csv_config'                   => 'CSV import configuration',
+    'specifix'                     => 'Bank- or file specific fixes',
+    'csv_import_account'           => 'Default import account',
+    'csv_delimiter'                => 'CSV field delimiter',
+    'attachments[]'                => 'Attachments',
+    'store_new_withdrawal'         => 'Store new withdrawal',
+    'store_new_deposit'            => 'Store new deposit',
+    'store_new_transfer'           => 'Store new transfer',
+    'add_new_withdrawal'           => 'Add a new withdrawal',
+    'add_new_deposit'              => 'Add a new deposit',
+    'add_new_transfer'             => 'Add a new transfer',
+    'noPiggybank'                  => '(no piggy bank)',
+    'title'                        => 'Title',
+    'notes'                        => 'Notes',
+    'filename'                     => 'File name',
+    'mime'                         => 'Mime type',
+    'size'                         => 'Size',
+    'trigger'                      => 'Trigger',
+    'stop_processing'              => 'Stop processing',
+    'start_date'                   => 'Start of range',
+    'end_date'                     => 'End of range',
+    'export_start_range'           => 'Start of export range',
+    'export_end_range'             => 'End of export range',
+    'export_format'                => 'File format',
+    'include_attachments'          => 'Include uploaded attachments',
+    'include_config'               => 'Include configuration file',
+    'include_old_uploads'          => 'Include imported data',
+    'accounts'                     => 'Export transactions from these accounts',
+    'csv_comma'                    => 'A comma (,)',
+    'csv_semicolon'                => 'A semicolon (;)',
+    'csv_tab'                      => 'A tab (invisible)',
+    'delete_account'               => 'Delete account ":name"',
+    'delete_bill'                  => 'Delete bill ":name"',
+    'delete_budget'                => 'Delete budget ":name"',
+    'delete_category'              => 'Delete category ":name"',
+    'delete_currency'              => 'Delete currency ":name"',
+    'delete_journal'               => 'Delete transaction with description ":description"',
+    'delete_attachment'            => 'Delete attachment ":name"',
+    'delete_rule'                  => 'Delete rule ":title"',
+    'delete_rule_group'            => 'Delete rule group ":title"',
+    'attachment_areYouSure'        => 'Are you sure you want to delete the attachment named ":name"?',
+    'account_areYouSure'           => 'Are you sure you want to delete the account named ":name"?',
+    'bill_areYouSure'              => 'Are you sure you want to delete the bill named ":name"?',
+    'rule_areYouSure'              => 'Are you sure you want to delete the rule titled ":title"?',
+    'ruleGroup_areYouSure'         => 'Are you sure you want to delete the rule group titled ":title"?',
+    'budget_areYouSure'            => 'Are you sure you want to delete the budget named ":name"?',
+    'category_areYouSure'          => 'Are you sure you want to delete the category named ":name"?',
+    'currency_areYouSure'          => 'Are you sure you want to delete the currency named ":name"?',
+    'piggyBank_areYouSure'         => 'Are you sure you want to delete the piggy bank named ":name"?',
+    'journal_areYouSure'           => 'Are you sure you want to delete the transaction described ":description"?',
+    'mass_journal_are_you_sure'    => 'Are you sure you want to delete these transactions?',
+    'tag_areYouSure'               => 'Are you sure you want to delete the tag ":tag"?',
+    'permDeleteWarning'            => 'Deleting stuff from Firely is permanent and cannot be undone.',
+    'mass_make_selection'          => 'You can still prevent items from being deleted by removing the checkbox.',
+    'delete_all_permanently'       => 'Delete selected permanently',
+    'update_all_journals'          => 'Update these transactions',
+    'also_delete_transactions'     => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
+    'also_delete_rules'            => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
+    'also_delete_piggyBanks'       => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
+    'bill_keep_transactions'       => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
+    'budget_keep_transactions'     => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
+    'category_keep_transactions'   => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
+    'tag_keep_transactions'        => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
 ];
diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php
index d894d21ee1..8a69fced9e 100644
--- a/resources/lang/en_US/list.php
+++ b/resources/lang/en_US/list.php
@@ -22,6 +22,9 @@ return [
     'balanceDiff'         => 'Balance difference between :start and :end',
     'matchedOn'           => 'Matched on',
     'matchesOn'           => 'Matched on',
+    'account_type'        => 'Account type',
+    'new_balance'         => 'New balance',
+    'account'             => 'Account',
     'matchingAmount'      => 'Amount',
     'lastMatch'           => 'Last match',
     'split_number'        => 'Split #',
diff --git a/resources/views/split/journals/edit.twig b/resources/views/split/journals/edit.twig
index e62198f22c..bde2f724d5 100644
--- a/resources/views/split/journals/edit.twig
+++ b/resources/views/split/journals/edit.twig
@@ -8,7 +8,7 @@
 
     
     
-    
+    
 
     {% if errors.all()|length > 0 %}
         
@@ -42,24 +42,23 @@
- {{ ExpandedForm.text('journal_description') }} - {{ ExpandedForm.select('journal_currency_id', currencies, journal.transaction_currency_id) }} - {{ ExpandedForm.staticText('journal_amount', amount|formatAmount ) }} - + {{ ExpandedForm.text('journal_description', journal.description) }} + {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.transaction_currency_id) }} + {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} + {% if what == 'withdrawal' or what == 'transfer' %} - {{ ExpandedForm.staticText('journal_asset_source_account', assetAccounts[sourceAccounts.first.id]) }} - + {{ ExpandedForm.select('journal_asset_source_account_id', assetAccounts, preFilled.sourceAccounts.first.id) }} {% endif %} {% if what == 'deposit' %} {{ ExpandedForm.staticText('revenue_account', sourceAccounts.first.name) }} - + {% endif %} {% if what == 'transfer' %} - {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[sourceAccounts.first.id]) }} - + {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[preFilled.destinationAccounts.first.id]) }} + {% endif %}
@@ -103,27 +102,27 @@ {{ trans('list.description') }} - {% if data.what == 'withdrawal' or data.what == 'deposit' %} + {% if preFilled.what == 'withdrawal' or preFilled.what == 'deposit' %} {{ trans('list.destination') }} {% endif %} {{ trans('list.amount') }} - {% if data.what == 'withdrawal' %} + {% if preFilled.what == 'withdrawal' %} {{ trans('list.budget') }} {% endif %} {{ trans('list.category') }} - {% if data.what == 'transfer' %} + {% if preFilled.what == 'transfer' %} {{ trans('list.piggy_bank') }} {% endif %} - {% for index, descr in data.description %} + {% for index, descr in preFilled.description %} #{{ loop.index }} @@ -131,36 +130,36 @@ - {% if data.what == 'withdrawal' %} + {% if preFilled.what == 'withdrawal' %} - {% endif %} - {% if data.what == 'deposit' %} + {% if preFilled.what == 'deposit' %} - {{ Form.select('destination_account_id[]', assetAccounts, data.destination_account_id[index], {class: 'form-control'}) }} + {{ Form.select('destination_account_id[]', assetAccounts, preFilled.destination_account_id[index], {class: 'form-control'}) }} {% endif %} - - {% if data.what == 'withdrawal' %} + {% if preFilled.what == 'withdrawal' %} - {{ Form.select('budget_id[]', budgets, data.budget_id[index], {class: 'form-control'}) }} + {{ Form.select('budget_id[]', budgets, preFilled.budget_id[index], {class: 'form-control'}) }} {% endif %} - + - {% if data.what == 'transfer' %} + {% if preFilled.what == 'transfer' %} - {{ Form.select('piggy_bank_id[]', piggyBanks, data.piggy_bank_id[index], {class: 'form-control'}) }} + {{ Form.select('piggy_bank_id[]', piggyBanks, preFilled.piggy_bank_id[index], {class: 'form-control'}) }} {% endif %} @@ -173,7 +172,7 @@

@@ -185,8 +184,8 @@ {% endblock %} {% block scripts %} diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig index 10e1f88d5c..8fb955924b 100644 --- a/resources/views/split/journals/from-store.twig +++ b/resources/views/split/journals/from-store.twig @@ -5,12 +5,12 @@ {% endblock %} {% block content %} {{ Form.open({'class' : 'form-horizontal','id' : 'store','url' : route('split.journal.from-store.post')}) }} - +
-

{{ ('split_title_'~data.what)|_ }}

+

{{ ('split_title_'~preFilled.what)|_ }}

@@ -18,16 +18,16 @@

- {{ ('split_intro_one_'~data.what)|_ }} + {{ ('split_intro_one_'~preFilled.what)|_ }}

- {{ ('split_intro_two_'~data.what)|_ }} + {{ ('split_intro_two_'~preFilled.what)|_ }}

- {% if data.what =='deposit' %} - {{ trans(('firefly.split_intro_three_'~data.what), {total: 500|formatAmount, split_one: 425|formatAmount, split_two: 75|formatAmount})|raw }} + {% if preFilled.what =='deposit' %} + {{ trans(('firefly.split_intro_three_'~preFilled.what), {total: 500|formatAmount, split_one: 425|formatAmount, split_two: 75|formatAmount})|raw }} {% else %} - {{ trans(('firefly.split_intro_three_'~data.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} + {{ trans(('firefly.split_intro_three_'~preFilled.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} {% endif %}

This feature is experimental.

@@ -68,23 +68,21 @@
{{ ExpandedForm.text('journal_description') }} - {{ ExpandedForm.select('journal_currency_id', currencies, data.journal_currency_id) }} - {{ ExpandedForm.staticText('journal_amount', data.journal_amount|formatAmount) }} - - - {% if data.what == 'withdrawal' or data.what == 'transfer' %} - {{ ExpandedForm.staticText('journal_asset_source_account', assetAccounts[data.journal_source_account_id]) }} - + {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.journal_currency_id) }} + {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount) }} + + + + {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} + {{ ExpandedForm.select('journal_asset_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} {% endif %} - - {% if data.what == 'deposit' %} - {{ ExpandedForm.staticText('revenue_account', data.journal_source_account_name) }} - + + {% if preFilled.what == 'deposit' %} + {{ ExpandedForm.text('journal_source_account_name', preFilled.journal_source_account_name) }} {% endif %} - - {% if data.what == 'transfer' %} - {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.journal_destination_account_id]) }} - + + {% if preFilled.what == 'transfer' %} + {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} {% endif %}
@@ -99,13 +97,13 @@
- {{ ExpandedForm.date('date', data.date) }} + {{ ExpandedForm.date('date', preFilled.date) }} - {{ ExpandedForm.date('interest_date', data.interest_date) }} + {{ ExpandedForm.date('interest_date', preFilled.interest_date) }} - {{ ExpandedForm.date('book_date', data.book_date) }} + {{ ExpandedForm.date('book_date', preFilled.book_date) }} - {{ ExpandedForm.date('process_date', data.process_date) }} + {{ ExpandedForm.date('process_date', preFilled.process_date) }}
@@ -122,7 +120,7 @@

- {{ ('split_table_intro_'~data.what)|_ }} + {{ ('split_table_intro_'~preFilled.what)|_ }}

@@ -131,27 +129,24 @@ - {% if data.what == 'withdrawal' or data.what == 'deposit' %} + {% if preFilled.what == 'withdrawal' or preFilled.what == 'deposit' %} {% endif %} - {% if data.what == 'withdrawal' %} + {% if preFilled.what == 'withdrawal' %} {% endif %} - {% if data.what == 'transfer' %} - - {% endif %} - {% for index, descr in data.description %} + {% for index, descr in preFilled.description %} - {% if data.what == 'withdrawal' %} + {% if preFilled.what == 'withdrawal' %} {% endif %} - {% if data.what == 'deposit' %} + {% if preFilled.what == 'deposit' %} {% endif %} - {% if data.what == 'withdrawal' %} + {% if preFilled.what == 'withdrawal' %} {% endif %} - {% if data.what == 'transfer' %} - - {% endif %} {% endfor %} @@ -201,7 +191,7 @@

@@ -213,8 +203,8 @@ {% endblock %} {% block scripts %} From 2d8449ed68a6a1b67a0e78103f2ade302d60277a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 07:33:04 +0200 Subject: [PATCH 109/206] This should just about finished split transaction editing. --- app/Crud/Split/Journal.php | 39 ++++++++ app/Crud/Split/JournalInterface.php | 8 ++ .../Transaction/SplitController.php | 93 ++++++++++++++----- app/Http/Requests/SplitJournalFormRequest.php | 1 + resources/lang/en_US/form.php | 1 + resources/views/split/journals/edit.twig | 58 +++++++++--- .../views/split/journals/from-store.twig | 2 +- 7 files changed, 165 insertions(+), 37 deletions(-) diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php index bd0bfeb380..192a829778 100644 --- a/app/Crud/Split/Journal.php +++ b/app/Crud/Split/Journal.php @@ -64,6 +64,13 @@ class Journal implements JournalInterface ); $journal->save(); + foreach ($data['transactions'] as $transaction) { + $this->storeTransaction($journal, $transaction); + } + + $journal->completed = true; + $journal->save(); + return $journal; } @@ -116,6 +123,38 @@ class Journal implements JournalInterface } + /** + * @param TransactionJournal $journal + * @param array $data + * + * @return TransactionJournal + */ + public function updateJournal(TransactionJournal $journal, array $data): TransactionJournal + { + echo '
';
+        print_r($data);
+
+        $journal->description             = $data['journal_description'];
+        $journal->transaction_currency_id = $data['journal_currency_id'];
+        $journal->date                    = $data['date'];
+        $journal->interest_date           = $data['interest_date'];
+        $journal->book_date               = $data['book_date'];
+        $journal->process_date            = $data['process_date'];
+        $journal->save();
+
+        // delete original transactions, and recreate them.
+        $journal->transactions()->delete();
+
+        foreach ($data['transactions'] as $transaction) {
+            $this->storeTransaction($journal, $transaction);
+        }
+
+        $journal->completed = true;
+        $journal->save();
+
+        return $journal;
+    }
+
     /**
      * @param string $type
      * @param array  $transaction
diff --git a/app/Crud/Split/JournalInterface.php b/app/Crud/Split/JournalInterface.php
index 00c00a5cba..709f0abd45 100644
--- a/app/Crud/Split/JournalInterface.php
+++ b/app/Crud/Split/JournalInterface.php
@@ -36,4 +36,12 @@ interface JournalInterface
      * @return Collection
      */
     public function storeTransaction(TransactionJournal $journal, array $transaction): Collection;
+
+    /**
+     * @param TransactionJournal $journal
+     * @param array              $data
+     *
+     * @return TransactionJournal
+     */
+    public function updateJournal(TransactionJournal $journal, array $data): TransactionJournal;
 }
\ No newline at end of file
diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php
index 27ed8387d2..275318fc26 100644
--- a/app/Http/Controllers/Transaction/SplitController.php
+++ b/app/Http/Controllers/Transaction/SplitController.php
@@ -12,6 +12,7 @@ namespace FireflyIII\Http\Controllers\Transaction;
 
 use ExpandedForm;
 use FireflyIII\Crud\Split\JournalInterface;
+use FireflyIII\Events\TransactionJournalUpdated;
 use FireflyIII\Exceptions\FireflyException;
 use FireflyIII\Http\Controllers\Controller;
 use FireflyIII\Http\Requests\SplitJournalFormRequest;
@@ -21,8 +22,12 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
 use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
 use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
 use Illuminate\Http\Request;
+use Input;
 use Log;
+use Preferences;
 use Session;
+use Steam;
+use URL;
 use View;
 
 /**
@@ -63,16 +68,24 @@ class SplitController extends Controller
         /** @var BudgetRepositoryInterface $budgetRepository */
         $budgetRepository = app(BudgetRepositoryInterface::class);
 
+        $uploadSize    = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
         $currencies    = ExpandedForm::makeSelectList($currencyRepository->get());
         $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account']));
         $budgets       = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
         $preFilled     = $this->arrayFromJournal($request, $journal);
 
-        // get the transactions:
+        Session::flash('gaEventCategory', 'transactions');
+        Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']);
+
+        // put previous url in session if not redirect from store (not "return_to_edit").
+        if (session('transactions.edit-split.fromUpdate') !== true) {
+            Session::put('transactions.edit-split.url', URL::previous());
+        }
+        Session::forget('transactions.edit-split.fromUpdate');
 
         return view(
             'split.journals.edit',
-            compact('currencies', 'preFilled', 'amount', 'sourceAccounts', 'destinationAccounts', 'assetAccounts', 'budgets', 'what', 'journal')
+            compact('currencies', 'preFilled', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts', 'budgets', 'journal')
         );
     }
 
@@ -105,7 +118,6 @@ class SplitController extends Controller
         $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account']));
         $budgets       = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
 
-
         return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets', 'preFilled'));
 
 
@@ -129,13 +141,6 @@ class SplitController extends Controller
         if (is_null($journal->id)) {
             throw new FireflyException('Could not store transaction.');
         }
-        foreach ($data['transactions'] as $transaction) {
-            $transactions = $repository->storeTransaction($journal, $transaction);
-        }
-
-        // TODO move to repository.
-        $journal->completed = true;
-        $journal->save();
 
         // forget temp journal data
         Session::forget('temporary_split_data');
@@ -145,12 +150,38 @@ class SplitController extends Controller
     }
 
     /**
+     * @param TransactionJournal      $journal
      * @param SplitJournalFormRequest $request
      * @param JournalInterface        $repository
+     *
+     * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      */
-    public function update(SplitJournalFormRequest $request, JournalInterface $repository)
+    public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository)
     {
-        echo 'ok';
+
+        $data    = $request->getSplitData();
+        $journal = $repository->updateJournal($journal, $data);
+
+        event(new TransactionJournalUpdated($journal));
+        // update, get events by date and sort DESC
+
+        $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
+        Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])])));
+        Preferences::mark();
+
+        if (intval(Input::get('return_to_edit')) === 1) {
+            // set value so edit routine will not overwrite URL:
+            Session::put('transactions.edit-split.fromUpdate', true);
+
+            return redirect(route('split.journal.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
+        }
+
+        // redirect to previous URL.
+        return redirect(session('transactions.edit-split.url'));
+
+
+        // update all:
+
     }
 
     /**
@@ -184,28 +215,40 @@ class SplitController extends Controller
             'budget_id'                => [],
             'category'                 => [],
         ];
-        $index = 0;
+        $index          = 0;
         /** @var Transaction $transaction */
         foreach ($journal->transactions()->get() as $transaction) {
-            //if ($journal->isWithdrawal() && $transaction->account_id !== $firstSourceId) {
-                $array['description'][]              = $transaction->description;
-                $array['destination_account_id'][]   = $transaction->account_id;
-                $array['destination_account_name'][] = $transaction->account->name;
-                $array['amount'][]                   = $transaction->amount;
-            //}
-            $budget   = $transaction->budgets()->first();
-            $budgetId = 0;
+            $budget       = $transaction->budgets()->first();
+            $category     = $transaction->categories()->first();
+            $budgetId     = 0;
+            $categoryName = '';
             if (!is_null($budget)) {
                 $budgetId = $budget->id;
             }
 
-            $category     = $transaction->categories()->first();
-            $categoryName = '';
             if (!is_null($category)) {
                 $categoryName = $category->name;
             }
-            $array['budget_id'][] = $budgetId;
-            $array['category'][]  = $categoryName;
+
+            $budgetId        = $request->old('budget_id')[$index] ?? $budgetId;
+            $categoryName    = $request->old('category')[$index] ?? $categoryName;
+            $amount          = $request->old('amount')[$index] ?? $transaction->amount;
+            $description     = $request->old('description')[$index] ?? $transaction->description;
+            $destinationName = $request->old('destination_account_name')[$index] ??$transaction->account->name;
+
+
+            if ($journal->isWithdrawal() && $transaction->account_id !== $firstSourceId) {
+                $array['description'][]              = $description;
+                $array['destination_account_id'][]   = $transaction->account_id;
+                $array['destination_account_name'][] = $destinationName;
+                $array['amount'][]                   = $amount;
+                $array['budget_id'][]                = intval($budgetId);
+                $array['category'][]                 = $categoryName;
+                // only add one when "valid" transaction
+                $index++;
+            }
+
+
         }
 
         return $array;
diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php
index 142796293d..d98cd06a4c 100644
--- a/app/Http/Requests/SplitJournalFormRequest.php
+++ b/app/Http/Requests/SplitJournalFormRequest.php
@@ -35,6 +35,7 @@ class SplitJournalFormRequest extends Request
     public function getSplitData(): array
     {
         $data = [
+            'id'                               => $this->get('id') ?? 0,
             'journal_description'              => $this->get('journal_description'),
             'journal_currency_id'              => intval($this->get('journal_currency_id')),
             'journal_source_account_id'        => intval($this->get('journal_source_account_id')),
diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
index 6e2c70cd48..2bead9d151 100644
--- a/resources/lang/en_US/form.php
+++ b/resources/lang/en_US/form.php
@@ -25,6 +25,7 @@ return [
     'journal_currency_id'          => 'Currency',
     'journal_amount'               => 'Amount',
     'journal_asset_source_account' => 'Asset account (source)',
+    'journal_source_account_id'    => 'Asset account (source)',
     'account_from_id'              => 'From account',
     'account_to_id'                => 'To account',
     'asset_destination_account'    => 'Asset account (destination)',
diff --git a/resources/views/split/journals/edit.twig b/resources/views/split/journals/edit.twig
index bde2f724d5..8f94f1a411 100644
--- a/resources/views/split/journals/edit.twig
+++ b/resources/views/split/journals/edit.twig
@@ -46,19 +46,17 @@
                     {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.transaction_currency_id) }}
                     {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }}
                     
-                    
-                    {% if what == 'withdrawal' or what == 'transfer' %}
-                        {{ ExpandedForm.select('journal_asset_source_account_id', assetAccounts, preFilled.sourceAccounts.first.id) }}
+                    
+                    {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %}
+                        {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }}
                     {% endif %}
                     
-                    {% if what == 'deposit' %}
-                        {{ ExpandedForm.staticText('revenue_account', sourceAccounts.first.name) }}
-                        
+                    {% if preFilled.what == 'deposit' %}
+                        {{ ExpandedForm.text('journal_source_account_name', preFilled.journal_source_account_name) }}
                     {% endif %}
                     
-                    {% if what == 'transfer' %}
-                        {{ ExpandedForm.staticText('asset_destination_account', assetAccounts[preFilled.destinationAccounts.first.id]) }}
-                        
+                    {% if preFilled.what == 'transfer' %}
+                        {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }}
                     {% endif %}
                 
             
@@ -82,6 +80,7 @@
                     {{ ExpandedForm.date('process_date', journal.process_date) }}
                 
             
+
         
     
     
@@ -151,7 +150,15 @@ {% if preFilled.what == 'withdrawal' %}
{% endif %} - + - + {% if t.description %} @@ -203,6 +203,48 @@ {% endfor %} {% endif %} + + + {% if journal.attachments|length > 0 and transactions.count > 2 %} +
+
+

{{ 'attachments'|_ }}

+
+
+
{{ trans('list.description') }} {{ trans('list.destination') }}{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}{{ trans('list.piggy_bank') }}
#{{ loop.index }} @@ -159,38 +154,33 @@ - - {{ Form.select('destination_account_id[]', assetAccounts, data.destination_account_id[index], {class: 'form-control'}) }} + {{ Form.select('destination_account_id[]', assetAccounts, preFilled.destination_account_id[index], {class: 'form-control'}) }} - - {{ Form.select('budget_id[]', budgets, data.budget_id[index], {class: 'form-control'}) }} + {{ Form.select('budget_id[]', budgets, preFilled.budget_id[index], {class: 'form-control'}) }} - + - {{ Form.select('piggy_bank_id[]', piggyBanks, data.piggy_bank_id[index], {class: 'form-control'}) }} -
- {{ Form.select('budget_id[]', budgets, preFilled.budget_id[index], {class: 'form-control'}) }} + @@ -170,11 +177,40 @@
{{ 'add_another_split'|_ }}

-

+ + + + + +

+
+ +
+
+

{{ 'optionalFields'|_ }}

+
+
+ + {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} +
+
+
+
+ +
+
+

{{ 'options'|_ }}

+
+
+ {{ ExpandedForm.optionsList('update','split-transaction') }} +
+
diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/from-store.twig index 8fb955924b..3c62e07552 100644 --- a/resources/views/split/journals/from-store.twig +++ b/resources/views/split/journals/from-store.twig @@ -74,7 +74,7 @@ {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} - {{ ExpandedForm.select('journal_asset_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} + {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} {% endif %} {% if preFilled.what == 'deposit' %} From aa592277868b38e445a7e1a054b0e74dc741ae1b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 09:55:06 +0200 Subject: [PATCH 110/206] Fixed transactions and attachments. --- .../Transaction/SplitController.php | 19 +++++-- .../Controllers/TransactionController.php | 17 ++++-- app/Models/TransactionJournal.php | 3 ++ .../Account/AccountRepository.php | 1 + .../Journal/JournalRepository.php | 38 ++++++++++++++ .../Journal/JournalRepositoryInterface.php | 10 ++++ resources/views/split/journals/edit.twig | 3 +- resources/views/transactions/show.twig | 52 +++++++++++++++++-- 8 files changed, 130 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 275318fc26..e4830db142 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -14,6 +14,7 @@ use ExpandedForm; use FireflyIII\Crud\Split\JournalInterface; use FireflyIII\Events\TransactionJournalUpdated; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SplitJournalFormRequest; use FireflyIII\Models\Transaction; @@ -150,21 +151,31 @@ class SplitController extends Controller } /** - * @param TransactionJournal $journal - * @param SplitJournalFormRequest $request - * @param JournalInterface $repository + * @param TransactionJournal $journal + * @param SplitJournalFormRequest $request + * @param JournalInterface $repository + * @param AttachmentHelperInterface $att * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository) + public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository, AttachmentHelperInterface $att) { $data = $request->getSplitData(); $journal = $repository->updateJournal($journal, $data); + // save attachments: + $att->saveAttachmentsForModel($journal); + event(new TransactionJournalUpdated($journal)); // update, get events by date and sort DESC + // flash messages + if (count($att->getMessages()->get('attachments')) > 0) { + Session::flash('info', $att->getMessages()->get('attachments')); + } + + $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal)); Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])]))); Preferences::mark(); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index ec3f07f866..6e731b4d68 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -22,6 +22,7 @@ use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; @@ -408,7 +409,7 @@ class TransactionController extends Controller * * @return \Illuminate\View\View */ - public function show(TransactionJournal $journal) + public function show(TransactionJournal $journal, JournalRepositoryInterface $repository) { /** @var Collection $set */ @@ -420,11 +421,21 @@ class TransactionController extends Controller ); // TODO different for each transaction type! + /** @var Collection $transactions */ $transactions = $journal->transactions()->groupBy('transactions.account_id')->orderBy('amount', 'ASC')->get( ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] ); - $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); - $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; + + // foreach do balance thing + $transactions->each( + function (Transaction $t) use ($repository) { + $t->before = $repository->balanceBeforeTransaction($t); + } + ); + + + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions')); } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 2557b67407..92a5e45e59 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -319,6 +319,9 @@ class TransactionJournal extends TransactionJournalSupport return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); } + /** + * @param EloquentBuilder $query + */ public function scopeSortCorrectly(EloquentBuilder $query) { $query->orderBy('transaction_journals.date', 'DESC'); diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index d0f8631b8a..426b996ae8 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -435,6 +435,7 @@ class AccountRepository implements AccountRepositoryInterface } /** + * * @param Account $account * @param Carbon $date * diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 7d05411f8c..f24b9ea15b 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -41,6 +41,44 @@ class JournalRepository implements JournalRepositoryInterface $this->user = $user; } + /** + * Returns the amount in the account before the specified transaction took place. + * + * @param Transaction $transaction + * + * @return string + */ + public function balanceBeforeTransaction(Transaction $transaction): string + { + // some dates from journal + $journal = $transaction->transactionJournal; + $query = Transaction:: + leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transactions.account_id', $transaction->account_id) + ->where('transaction_journals.user_id', $this->user->id) + ->where( + function (Builder $q) use ($journal) { + $q->where('transaction_journals.date', '<', $journal->date->format('Y-m-d')); + $q->orWhere( + function (Builder $qq) use ($journal) { + $qq->where('transaction_journals.date', '=', $journal->date->format('Y-m-d')); + $qq->where('transaction_journals.order', '>', $journal->order); + } + ); + + } + ) + ->where('transactions.id', '!=', $transaction->id) + ->whereNull('transactions.deleted_at') + ->whereNull('transaction_journals.deleted_at') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC'); + $sum = $query->sum('transactions.amount'); + + return strval($sum); + } + /** * @param TransactionJournal $journal * diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index a31c8cf076..76677f8a21 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -41,6 +41,16 @@ interface JournalRepositoryInterface */ public function first(): TransactionJournal; + + /** + * Returns the amount in the account before the specified transaction took place. + * + * @param Transaction $transaction + * + * @return string + */ + public function balanceBeforeTransaction(Transaction $transaction): string; + /** * Returns the amount in the account before the specified transaction took place. * diff --git a/resources/views/split/journals/edit.twig b/resources/views/split/journals/edit.twig index 8f94f1a411..6299967e51 100644 --- a/resources/views/split/journals/edit.twig +++ b/resources/views/split/journals/edit.twig @@ -4,7 +4,8 @@ {{ Breadcrumbs.renderIfExists }} {% endblock %} {% block content %} - {{ Form.model(journal, {'class' : 'form-horizontal','id' : 'update','url' : route('split.journal.update',journal.id) } ) }} +
diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 610d21acff..525697a868 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -100,8 +100,8 @@
- - {% if journal.attachments|length > 0 %} + + {% if journal.attachments|length > 0 and transactions.count == 2 %}

{{ 'attachments'|_ }}

@@ -171,11 +171,11 @@
{{ 'amount'|_ }}{{ t|formatTransaction }}{{ t.before|formatAmount}}
{{ 'newBalance'|_ }}null{{ (t.before+t.amount)|formatAmount }}
+ {% for att in journal.attachments %} + + + + + + {% endfor %} +
+
+ + +
+
+ + + {% if att.title %} + {{ att.title }} + {% else %} + {{ att.filename }} + {% endif %} + + ({{ att.size|filesize }}) + {% if att.description %} +
+ {{ att.description }} + {% endif %} +
+ +
+
+
+ {% endif %} +
@@ -233,7 +275,7 @@ {{ t.account.name }} {{ t.account.accounttype.type|_ }} {{ t.sum|formatAmount }} - null + {{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }} {{ t.description }} {% if t.categories[0] %} From 36bc483edbf219ec8b8f13c275038f7d5eb29c9a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 10:50:19 +0200 Subject: [PATCH 111/206] Reorder some fields. --- resources/views/transactions/show.twig | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 525697a868..d514ef243b 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -161,6 +161,7 @@

{{ t.account.name }}

+ @@ -260,33 +261,32 @@
{{ 'account'|_ }} {{ t.account.name }}
+ - - - + {% for t in transactions %} - - + + - - + + {% endfor %}
{{ trans('list.description') }} {{ trans('list.account') }}{{ trans('list.account_type') }} {{ trans('list.amount') }} {{ trans('list.new_balance') }}{{ trans('list.description') }}{{ trans('list.category') }} {{ trans('list.budget') }}{{ trans('list.category') }}
{{ t.account.name }}{{ t.account.accounttype.type|_ }}{{ t.description }}{{ t.account.name }} ({{ t.account.accounttype.type|_ }}) {{ t.sum|formatAmount }} {{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }}{{ t.description }} - {% if t.categories[0] %} - {{ t.categories[0].name }} - {% endif %} - {% if t.budgets[0] %} {{ t.budgets[0].name }} {% endif %} + {% if t.categories[0] %} + {{ t.categories[0].name }} + {% endif %} +
From 20e1e50032e0171f8e2ad6a9c443d0890796b45c Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 15:53:39 +0200 Subject: [PATCH 112/206] Refactoring. --- .../Csv/Converter/AssetAccountIban.php | 2 +- .../Csv/Converter/AssetAccountName.php | 2 +- .../Csv/Converter/AssetAccountNumber.php | 2 +- .../Csv/Converter/OpposingAccountIban.php | 2 +- app/Http/Controllers/AccountController.php | 90 ++++- app/Http/Controllers/BudgetController.php | 2 +- app/Http/Controllers/CategoryController.php | 27 +- .../Controllers/Chart/AccountController.php | 77 ++--- app/Http/Controllers/Chart/BillController.php | 25 +- .../Controllers/Chart/BudgetController.php | 10 +- .../Controllers/Chart/CategoryController.php | 8 +- app/Http/Controllers/CsvController.php | 2 +- app/Http/Controllers/ExportController.php | 2 +- app/Http/Controllers/HomeController.php | 14 +- app/Http/Controllers/JsonController.php | 30 +- app/Http/Controllers/PiggyBankController.php | 4 +- .../Controllers/Popup/ReportController.php | 8 +- .../Controllers/PreferencesController.php | 2 +- app/Http/Controllers/ReportController.php | 2 +- app/Http/Controllers/RuleGroupController.php | 2 +- .../Transaction/SplitController.php | 4 +- .../Controllers/TransactionController.php | 6 +- app/Http/routes.php | 4 +- app/Models/AccountType.php | 9 + .../Account/AccountRepository.php | 311 ++++++++++-------- .../Account/AccountRepositoryInterface.php | 114 +++---- app/Repositories/Bill/BillRepository.php | 49 --- .../Bill/BillRepositoryInterface.php | 11 - app/Support/Navigation.php | 2 - resources/views/accounts/show.twig | 33 +- resources/views/accounts/show_with_date.twig | 62 ++++ 31 files changed, 526 insertions(+), 392 deletions(-) create mode 100644 resources/views/accounts/show_with_date.twig diff --git a/app/Helpers/Csv/Converter/AssetAccountIban.php b/app/Helpers/Csv/Converter/AssetAccountIban.php index 83c5dd2c34..c75bfc2c1f 100644 --- a/app/Helpers/Csv/Converter/AssetAccountIban.php +++ b/app/Helpers/Csv/Converter/AssetAccountIban.php @@ -50,7 +50,7 @@ class AssetAccountIban extends BasicConverter implements ConverterInterface private function searchOrCreate(AccountRepositoryInterface $repository) { // find or create new account: - $set = $repository->getAccounts(['Default account', 'Asset account']); + $set = $repository->getAccountsByType(['Default account', 'Asset account']); /** @var Account $entry */ foreach ($set as $entry) { if ($entry->iban == $this->value) { diff --git a/app/Helpers/Csv/Converter/AssetAccountName.php b/app/Helpers/Csv/Converter/AssetAccountName.php index cfe81f487a..017866ed06 100644 --- a/app/Helpers/Csv/Converter/AssetAccountName.php +++ b/app/Helpers/Csv/Converter/AssetAccountName.php @@ -28,7 +28,7 @@ class AssetAccountName extends BasicConverter implements ConverterInterface return $account; } - $set = $repository->getAccounts(['Default account', 'Asset account']); + $set = $repository->getAccountsByType(['Default account', 'Asset account']); /** @var Account $entry */ foreach ($set as $entry) { if ($entry->name == $this->value) { diff --git a/app/Helpers/Csv/Converter/AssetAccountNumber.php b/app/Helpers/Csv/Converter/AssetAccountNumber.php index 646504635f..e15cf4c900 100644 --- a/app/Helpers/Csv/Converter/AssetAccountNumber.php +++ b/app/Helpers/Csv/Converter/AssetAccountNumber.php @@ -42,7 +42,7 @@ class AssetAccountNumber extends BasicConverter implements ConverterInterface $value = $this->value ?? ''; if (strlen($value) > 0) { // find or create new account: - $set = $repository->getAccounts(['Default account', 'Asset account']); + $set = $repository->getAccountsByType(['Default account', 'Asset account']); /** @var Account $entry */ foreach ($set as $entry) { $accountNumber = $entry->getMeta('accountNumber'); diff --git a/app/Helpers/Csv/Converter/OpposingAccountIban.php b/app/Helpers/Csv/Converter/OpposingAccountIban.php index 902e7df0aa..7702419a39 100644 --- a/app/Helpers/Csv/Converter/OpposingAccountIban.php +++ b/app/Helpers/Csv/Converter/OpposingAccountIban.php @@ -41,7 +41,7 @@ class OpposingAccountIban extends BasicConverter implements ConverterInterface { if (strlen($this->value) > 0) { - $set = $repository->getAccounts([]); + $set = $repository->getAccountsByType([]); /** @var Account $account */ foreach ($set as $account) { if ($account->iban == $this->value) { diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 109d81aff2..3920a95155 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -6,8 +6,11 @@ use ExpandedForm; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; +use FireflyIII\Support\CacheProperties; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Input; +use Navigation; use Preferences; use Session; use Steam; @@ -66,7 +69,7 @@ class AccountController extends Controller { $typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type); $subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]); - $accountList = ExpandedForm::makeSelectListWithEmpty($repository->getAccounts([$account->accountType->type])); + $accountList = ExpandedForm::makeSelectListWithEmpty($repository->getAccountsByType([$account->accountType->type])); unset($accountList[$account->id]); // put previous url in session @@ -157,7 +160,7 @@ class AccountController extends Controller $subTitle = trans('firefly.' . $what . '_accounts'); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); $types = config('firefly.accountTypesByIdentifier.' . $what); - $accounts = $repository->getAccounts($types); + $accounts = $repository->getAccountsByType($types); /** @var Carbon $start */ $start = clone session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ @@ -180,6 +183,32 @@ class AccountController extends Controller return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts')); } + /** + * @param ARI $repository + * @param Account $account + * @param string $date + * + * @return View + */ + public function showWithDate(ARI $repository, Account $account, string $date) + { + $carbon = new Carbon($date); + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($carbon, $range); + $end = Navigation::endOfPeriod($carbon, $range); + $subTitle = $account->name; + $page = intval(Input::get('page')); + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $offset = ($page - 1) * $pageSize; + $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); + $count = $set->count(); + $subSet = $set->splice($offset, $pageSize); + $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); + $journals->setPath('categories/show/' . $account->id . '/' . $date); + + return view('accounts.show_with_date', compact('category', 'journals', 'subTitle', 'carbon')); + } + /** * @param ARI $repository * @param Account $account @@ -188,16 +217,59 @@ class AccountController extends Controller */ public function show(ARI $repository, Account $account) { - $page = intval(Input::get('page')) == 0 ? 1 : intval(Input::get('page')); - $pageSize = Preferences::get('transactionPageSize', 50)->data; - $subTitleIcon = config('firefly.subTitlesByIdentifier.' . $account->accountType->type); - $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); - $journals = $repository->getJournals($account, $page, $pageSize); - $subTitle = trans('firefly.details_for_' . $what, ['name' => $account->name]); + // show journals from current period only: + $range = Preferences::get('viewRange', '1M')->data; + $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); + $page = intval(Input::get('page')); + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $offset = ($page - 1) * $pageSize; + $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); + $count = $set->count(); + $subSet = $set->splice($offset, $pageSize); + $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); $journals->setPath('accounts/show/' . $account->id); + // grouped other months thing: + // oldest transaction in account: + $start = $repository->firstUseDate($account); + if ($start->year == 1900) { + $start = new Carbon; + } + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; - return view('accounts.show', compact('account', 'what', 'subTitleIcon', 'journals', 'subTitle')); + // chart properties for cache: + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('account-show'); + $cache->addProperty($account->id); + + + if ($cache->has()) { + //$entries = $cache->get(); + //return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle')); + } + + + $accountCollection = new Collection([$account]); + while ($end >= $start) { + $end = Navigation::startOfPeriod($end, $range); + $currentEnd = Navigation::endOfPeriod($end, $range); + $spent = $repository->spentInPeriod($accountCollection, $end, $currentEnd); + $earned = $repository->earnedInPeriod($accountCollection, $end, $currentEnd); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $entries->push([$dateStr, $dateName, $spent, $earned]); + $end = Navigation::subtractPeriod($end, $range, 1); + + } + $cache->store($entries); + + return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); } /** diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 8e3e1f6f87..3c1873010c 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -174,7 +174,7 @@ class BudgetController extends Controller $period = Navigation::periodShow($start, $range); $periodStart = $start->formatLocalized($this->monthAndDayFormat); $periodEnd = $end->formatLocalized($this->monthAndDayFormat); - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + $accounts = $accountRepository->getAccountsByType(['Default account', 'Asset account', 'Cash account']); $startAsString = $start->format('Y-m-d'); $endAsString = $end->format('Y-m-d'); diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 1a5895b2a3..8d1d8b3369 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -3,7 +3,6 @@ use Auth; use Carbon\Carbon; use FireflyIII\Http\Requests\CategoryFormRequest; -use FireflyIII\Models\Account; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Support\CacheProperties; @@ -153,16 +152,25 @@ class CategoryController extends Controller */ public function show(CRI $repository, Category $category) { + /** @var Carbon $carbon */ + $range = Preferences::get('viewRange', '1M')->data; + $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); $hideCategory = true; // used in list. - $pageSize = Preferences::get('transactionPageSize', 50)->data; $page = intval(Input::get('page')); - $journals = $repository->getJournals($category, $page, $pageSize); + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $offset = ($page - 1) * $pageSize; + $set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); + $count = $set->count(); + $subSet = $set->splice($offset, $pageSize); + $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); $journals->setPath('categories/show/' . $category->id); - // list of ranges for list of periods: - // oldest transaction in category: - $start = $repository->firstUseDate($category, new Collection); + $start = $repository->firstUseDate($category, new Collection); + if ($start->year == 1900) { + $start = new Carbon; + } $range = Preferences::get('viewRange', '1M')->data; $start = Navigation::startOfPeriod($start, $range); $end = Navigation::endOfX(new Carbon, $range); @@ -177,8 +185,9 @@ class CategoryController extends Controller if ($cache->has()) { - //$entries = $cache->get(); - //return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle')); + $entries = $cache->get(); + + return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle')); } @@ -223,7 +232,7 @@ class CategoryController extends Controller $set = $repository->journalsInPeriod(new Collection([$category]), new Collection, [], $start, $end); $count = $set->count(); $subSet = $set->splice($offset, $pageSize); - $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); + $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); $journals->setPath('categories/show/' . $category->id . '/' . $date); return view('categories.show_with_date', compact('category', 'journals', 'hideCategory', 'subTitle', 'carbon')); diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index b276785119..e0689b1754 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -8,10 +8,7 @@ use FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; -use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; -use Preferences; -use Response; /** checked * Class AccountController @@ -34,39 +31,6 @@ class AccountController extends Controller $this->generator = app(AccountChartGeneratorInterface::class); } - - /** - * Shows the balances for a given set of dates and accounts. - * - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return \Illuminate\Http\JsonResponse - */ - public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('all'); - $cache->addProperty('accounts'); - $cache->addProperty('default'); - $cache->addProperty($reportType); - $cache->addProperty($accounts); - if ($cache->has()) { - return Response::json($cache->get()); - } - - // make chart: - $data = $this->generator->frontpage($accounts, $start, $end); - $cache->store($data); - - return Response::json($data); - } - /** * Shows the balances for all the user's expense accounts. * @@ -76,6 +40,7 @@ class AccountController extends Controller */ public function expenseAccounts(ARI $repository) { + /* $start = clone session('start', Carbon::now()->startOfMonth()); $end = clone session('end', Carbon::now()->endOfMonth()); $accounts = $repository->getAccounts(['Expense account', 'Beneficiary account']); @@ -94,7 +59,7 @@ class AccountController extends Controller $cache->store($data); return Response::json($data); - +*/ } /** @@ -106,6 +71,7 @@ class AccountController extends Controller */ public function frontpage(ARI $repository) { + /* $frontPage = Preferences::get('frontPageAccounts', []); $start = clone session('start', Carbon::now()->startOfMonth()); $end = clone session('end', Carbon::now()->endOfMonth()); @@ -125,7 +91,41 @@ class AccountController extends Controller $cache->store($data); return Response::json($data); +*/ + } + /** + * Shows the balances for a given set of dates and accounts. + * + * @param $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return \Illuminate\Http\JsonResponse + */ + public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + { + /* + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('all'); + $cache->addProperty('accounts'); + $cache->addProperty('default'); + $cache->addProperty($reportType); + $cache->addProperty($accounts); + if ($cache->has()) { + return Response::json($cache->get()); + } + + // make chart: + $data = $this->generator->frontpage($accounts, $start, $end); + $cache->store($data); + + return Response::json($data); + */ } /** @@ -137,7 +137,7 @@ class AccountController extends Controller */ public function single(Account $account) { - + /* $start = clone session('start', Carbon::now()->startOfMonth()); $end = clone session('end', Carbon::now()->endOfMonth()); @@ -157,5 +157,6 @@ class AccountController extends Controller $cache->store($data); return Response::json($data); + */ } } diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index f0b654f7f4..5ba431f88a 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -42,26 +42,11 @@ class BillController extends Controller */ public function frontpage(BillRepositoryInterface $repository) { - $start = session('start', Carbon::now()->startOfMonth()); - $end = session('end', Carbon::now()->endOfMonth()); - $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. - $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. - $creditCardDue = $repository->getCreditCardBill($start, $end); - - if ($creditCardDue < 0) { - // expenses are negative (bill not yet paid), - $creditCardDue = bcmul($creditCardDue, '-1'); - $unpaid = bcadd($unpaid, $creditCardDue); - } - - // if $creditCardDue more than zero, the bill has been paid: (transfer = positive). - // amount must be negative to be added to $paid: - if ($creditCardDue >= 0) { - $paid = bcadd($paid, $creditCardDue); - } - - // build chart: - $data = $this->generator->frontpage($paid, $unpaid); + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); + $paid = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. + $unpaid = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. + $data = $this->generator->frontpage($paid, $unpaid); return Response::json($data); } diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index ce5132628b..094861e164 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -110,7 +110,7 @@ class BudgetController extends Controller $cache->addProperty($repetition->id); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } $entries = new Collection; @@ -150,7 +150,7 @@ class BudgetController extends Controller $cache->addProperty('budget'); $cache->addProperty('all'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } $budgets = $repository->getActiveBudgets(); $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); @@ -203,8 +203,6 @@ class BudgetController extends Controller $data = $this->generator->frontpage($allEntries); $cache->store($data); - return ' ' . json_encode($data); - return Response::json($data); } @@ -230,7 +228,7 @@ class BudgetController extends Controller $cache->addProperty('multiYearBudget'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } $budgetIds = $budgets->pluck('id')->toArray(); $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); @@ -300,7 +298,7 @@ class BudgetController extends Controller $cache->addProperty('budget'); $cache->addProperty('period'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } // loop over period, add by users range: $current = clone $start; diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 5e432520fe..b763da7ac2 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -64,7 +64,7 @@ class CategoryController extends Controller $cache->addProperty('all'); $cache->addProperty('categories'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } while ($start <= $end) { @@ -119,7 +119,7 @@ class CategoryController extends Controller $cache->addProperty('category'); $cache->addProperty('frontpage'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } $categories = $repository->getCategories(); $set = new Collection; @@ -169,7 +169,7 @@ class CategoryController extends Controller $cache->addProperty('multiYearCategory'); if ($cache->has()) { - //return Response::json($cache->get()); + return Response::json($cache->get()); } $entries = new Collection; @@ -237,7 +237,7 @@ class CategoryController extends Controller $cache->addProperty('category'); $cache->addProperty('period'); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } /** @var CRI $repository */ diff --git a/app/Http/Controllers/CsvController.php b/app/Http/Controllers/CsvController.php index feec892c4a..f26602564e 100644 --- a/app/Http/Controllers/CsvController.php +++ b/app/Http/Controllers/CsvController.php @@ -190,7 +190,7 @@ class CsvController extends Controller ]; // get a list of asset accounts: - $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Asset account', 'Default account'])); + $accounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Asset account', 'Default account'])); // can actually upload? $uploadPossible = is_writable(storage_path('upload')); diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 73853dcf49..262c2af1de 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -102,7 +102,7 @@ class ExportController extends Controller $jobs->cleanup(); // does the user have shared accounts? - $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accounts = $repository->getAccountsByType(['Default account', 'Asset account']); $accountList = ExpandedForm::makeSelectList($accounts); $checked = array_keys($accountList); $formats = array_keys(config('firefly.export_formats')); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 0aafa57c49..a6fbd9f4a2 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -7,6 +7,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use Illuminate\Support\Collection; use Input; use Preferences; use Route; @@ -116,7 +117,7 @@ class HomeController extends Controller /** @var Carbon $end */ $end = session('end', Carbon::now()->endOfMonth()); $showTour = Preferences::get('tour', true)->data; - $accounts = $repository->getFrontpageAccounts($frontPage); + $accounts = $repository->getAccountsById($frontPage->data); $savings = $repository->getSavingsAccounts(); $piggyBankAccounts = $repository->getPiggyBankAccounts(); @@ -133,7 +134,8 @@ class HomeController extends Controller } foreach ($accounts as $account) { - $set = $repository->getFrontpageTransactions($account, $start, $end); + $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); + $set = $set->splice(0, 10); if (count($set) > 0) { $transactions[] = [$set, $account]; @@ -153,10 +155,10 @@ class HomeController extends Controller { // these routes are not relevant for the help pages: $ignore = [ -// 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview', -// 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download', -// 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down', -// 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar', + // 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview', + // 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download', + // 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down', + // 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar', ]; $routes = Route::getRoutes(); /** @var \Illuminate\Routing\Route $route */ diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 27e31877f0..5ae73299b3 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -60,11 +60,7 @@ class JsonController extends Controller * Since both this method and the chart use the exact same data, we can suffice * with calling the one method in the bill repository that will get this amount. */ - $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. - $creditCardDue = $repository->getCreditCardBill($start, $end); - if ($creditCardDue >= 0) { - $amount = bcadd($amount, $creditCardDue); - } + $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. $amount = bcmul($amount, '-1'); $data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; @@ -79,18 +75,10 @@ class JsonController extends Controller */ public function boxBillsUnpaid(BillRepositoryInterface $repository) { - $start = session('start', Carbon::now()->startOfMonth()); - $end = session('end', Carbon::now()->endOfMonth()); - $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. - $creditCardDue = $repository->getCreditCardBill($start, $end); - - if ($creditCardDue < 0) { - // expenses are negative (bill not yet paid), - $creditCardDue = bcmul($creditCardDue, '-1'); - $amount = bcadd($amount, $creditCardDue); - } - - $data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); + $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. + $data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; return Response::json($data); } @@ -115,7 +103,7 @@ class JsonController extends Controller if ($cache->has()) { return Response::json($cache->get()); } - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + $accounts = $accountRepository->getAccountsByType(['Default account', 'Asset account', 'Cash account']); $amount = $reportQuery->income($accounts, $start, $end)->sum('journalAmount'); $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; @@ -136,7 +124,7 @@ class JsonController extends Controller $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + $accounts = $accountRepository->getAccountsByType(['Default account', 'Asset account', 'Cash account']); // works for json too! $cache = new CacheProperties; @@ -192,7 +180,7 @@ class JsonController extends Controller */ public function expenseAccounts(ARI $accountRepository) { - $list = $accountRepository->getAccounts(['Expense account', 'Beneficiary account']); + $list = $accountRepository->getAccountsByType(['Expense account', 'Beneficiary account']); $return = []; foreach ($list as $entry) { $return[] = $entry->name; @@ -209,7 +197,7 @@ class JsonController extends Controller */ public function revenueAccounts(ARI $accountRepository) { - $list = $accountRepository->getAccounts(['Revenue account']); + $list = $accountRepository->getAccountsByType(['Revenue account']); $return = []; foreach ($list as $entry) { $return[] = $entry->name; diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 8619352932..3b3af3171f 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -65,7 +65,7 @@ class PiggyBankController extends Controller { $periods = config('firefly.piggy_bank_periods'); - $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); + $accounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account'])); $subTitle = trans('firefly.new_piggy_bank'); $subTitleIcon = 'fa-plus'; @@ -124,7 +124,7 @@ class PiggyBankController extends Controller { $periods = config('firefly.piggy_bank_periods'); - $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); + $accounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account'])); $subTitle = trans('firefly.update_piggy_title', ['name' => $piggyBank->name]); $subTitleIcon = 'fa-pencil'; $targetDate = null; diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index 07280a4a3e..85dc7ef1b6 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -16,6 +16,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collection\BalanceLine; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -182,7 +183,8 @@ class ReportController extends Controller /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $account = $repository->find(intval($attributes['accountId'])); - $journals = $repository->getExpensesByDestination($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $journals = $repository->journalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']); $view = view('popup.report.expense-entry', compact('journals', 'account'))->render(); return $view; @@ -201,10 +203,10 @@ class ReportController extends Controller /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $account = $repository->find(intval($attributes['accountId'])); - $journals = $repository->getIncomeByDestination($account, $attributes['accounts'], $attributes['startDate'], $attributes['endDate']); + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $journals = $repository->journalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']); $view = view('popup.report.income-entry', compact('journals', 'account'))->render(); - return $view; } diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index e7106a218b..8c27ae274c 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -63,7 +63,7 @@ class PreferencesController extends Controller */ public function index(ARI $repository) { - $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accounts = $repository->getAccountsByType(['Default account', 'Asset account']); $viewRangePref = Preferences::get('viewRange', '1M'); $viewRange = $viewRangePref->data; $frontPageAccounts = Preferences::get('frontPageAccounts', []); diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 21e76eef1d..e49f3e9487 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -69,7 +69,7 @@ class ReportController extends Controller $customFiscalYear = Preferences::get('customFiscalYear', 0)->data; // does the user have shared accounts? - $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accounts = $repository->getAccountsByType(['Default account', 'Asset account']); // get id's for quick links: $accountIds = []; /** @var Account $account */ diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 622f47e3e8..488bbc7cc3 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -178,7 +178,7 @@ class RuleGroupController extends Controller public function selectTransactions(AccountRepositoryInterface $repository, RuleGroup $ruleGroup) { // does the user have shared accounts? - $accounts = $repository->getAccounts(['Default account', 'Asset account']); + $accounts = $repository->getAccountsByType(['Default account', 'Asset account']); $accountList = ExpandedForm::makeSelectList($accounts); $checkedAccounts = array_keys($accountList); $first = session('first')->format('Y-m-d'); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index e4830db142..f370205019 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -71,7 +71,7 @@ class SplitController extends Controller $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); $preFilled = $this->arrayFromJournal($request, $journal); @@ -116,7 +116,7 @@ class SplitController extends Controller $budgetRepository = app(BudgetRepositoryInterface::class); $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets', 'preFilled')); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 6e731b4d68..ee79095b42 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -72,7 +72,7 @@ class TransactionController extends Controller $what = strtolower($what); $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $assetAccounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); + $assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); $piggyBanks = $piggyRepository->getPiggyBanks(); /** @var PiggyBank $piggy */ @@ -159,7 +159,7 @@ class TransactionController extends Controller /** @var PiggyBankRepositoryInterface $piggyRepository */ $piggyRepository = app(PiggyBankRepositoryInterface::class); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks()); $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); @@ -296,7 +296,7 @@ class TransactionController extends Controller $subTitle = trans('firefly.mass_edit_journals'); /** @var ARI $accountRepository */ $accountRepository = app(ARI::class); - $accountList = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account'])); + $accountList = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); // put previous url in session Session::put('transactions.mass-edit.url', URL::previous()); diff --git a/app/Http/routes.php b/app/Http/routes.php index aa141631e6..ed96303917 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -83,7 +83,9 @@ Route::group( Route::get('/accounts/create/{what}', ['uses' => 'AccountController@create', 'as' => 'accounts.create'])->where('what', 'revenue|asset|expense'); Route::get('/accounts/edit/{account}', ['uses' => 'AccountController@edit', 'as' => 'accounts.edit']); Route::get('/accounts/delete/{account}', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']); - Route::get('/accounts/show/{account}/{view?}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); + Route::get('/accounts/show/{account}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); + Route::get('/accounts/show/{account}/{date}', ['uses' => 'AccountController@showWithDate', 'as' => 'accounts.show.date']); + Route::post('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']); Route::post('/accounts/update/{account}', ['uses' => 'AccountController@update', 'as' => 'accounts.update']); diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index f9b6757fd1..d5780ce246 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -21,6 +21,15 @@ use Illuminate\Database\Eloquent\Relations\HasMany; */ class AccountType extends Model { + const DEFAULT = 'Default account'; + const CASH = 'Cash account'; + const ASSET = 'Asset account'; + const EXPENSE = 'Expense account'; + const REVENUE = 'Revenue account'; + const INITIAL_BALANCE = 'Initial balance account'; + const BENEFICIARY = 'Beneficiary account'; + const IMPORT = 'Import account'; + protected $dates = ['created_at', 'updated_at']; diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 426b996ae8..f1d8292edc 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -8,7 +8,6 @@ use FireflyIII\Models\Account; use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; use FireflyIII\Models\PiggyBank; -use FireflyIII\Models\Preference; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -77,6 +76,23 @@ class AccountRepository implements AccountRepositoryInterface return true; } + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriod(Collection $accounts, Carbon $start, Carbon $end): string + { + Log::debug('earnedinperiod'); + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $sum = bcmul($this->sumInPeriod($accounts, $types, $start, $end), '-1'); + + return $sum; + + } + /** * @param $accountId * @@ -92,6 +108,29 @@ class AccountRepository implements AccountRepositoryInterface return $account; } + /** + * @param Account $account + * + * @return Carbon + */ + public function firstUseDate(Account $account): Carbon + { + $first = new Carbon('1900-01-01'); + + /** @var Transaction $first */ + $date = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'ASC') + ->orderBy('transaction_journals.order', 'DESC') + ->orderBy('transaction_journals.id', 'ASC') + ->first(['transaction_journals.date']); + if (!is_null($date)) { + $first = new Carbon($date->date); + } + + return $first; + } + /** * Gets all the accounts by ID, for a given set. * @@ -105,11 +144,11 @@ class AccountRepository implements AccountRepositoryInterface } /** - * @param array $types + * @param array $accountIds * * @return Collection */ - public function getAccounts(array $types): Collection + public function getAccountsById(array $accountIds): Collection { /** @var Collection $result */ $query = $this->user->accounts()->with( @@ -117,9 +156,11 @@ class AccountRepository implements AccountRepositoryInterface $query->where('name', 'accountRole'); }] ); - if (count($types) > 0) { - $query->accountTypeIn($types); + + if (count($accountIds) > 0) { + $query->whereIn('accounts.id', $accountIds); } + $result = $query->get(['accounts.*']); $result = $result->sortBy( @@ -132,72 +173,31 @@ class AccountRepository implements AccountRepositoryInterface } /** - * This method returns the users credit cards, along with some basic information about the - * balance they have on their CC. To be used in the JSON boxes on the front page that say - * how many bills there are still left to pay. The balance will be saved in field "balance". - * - * To get the balance, the field "date" is necessary. - * - * @param Carbon $date + * @param array $types * * @return Collection */ - public function getCreditCards(Carbon $date): Collection + public function getAccountsByType(array $types): Collection { - $set = $this->user->accounts() - ->hasMetaValue('accountRole', 'ccAsset') - ->hasMetaValue('ccType', 'monthlyFull') - ->leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->whereNull('transactions.deleted_at') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d')) - ->groupBy('accounts.id') - ->get( - [ - 'accounts.*', - 'ccType.data as ccType', - 'accountRole.data as accountRole', - DB::Raw('SUM(`transactions`.`amount`) AS `balance`'), - ] - ); + /** @var Collection $result */ + $query = $this->user->accounts()->with( + ['accountmeta' => function (HasMany $query) { + $query->where('name', 'accountRole'); + }] + ); + if (count($types) > 0) { + $query->accountTypeIn($types); + } - return $set; - } + $result = $query->get(['accounts.*']); - /** - * Returns a list of transactions TO the $account, not including transfers - * and/or expenses in the $accounts list. - * - * @param Account $account - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getExpensesByDestination(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); - $journals = $this->user->transactionjournals() - ->expanded() - ->sortCorrectly() - ->before($end) - ->leftJoin( - 'transactions as dest', function (JoinClause $join) { - $join->on('dest.transaction_journal_id', '=', 'transaction_journals.id')->where('dest.amount', '>', 0); - } - ) - ->leftJoin( - 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); - } - ) - ->where('dest.account_id', $account->id) - ->whereIn('source.account_id', $ids) - ->after($start) - ->get(TransactionJournal::queryFields()); + $result = $result->sortBy( + function (Account $account) { + return strtolower($account->name); + } + ); - return $journals; + return $result; } /** @@ -216,80 +216,6 @@ class AccountRepository implements AccountRepositoryInterface return $transaction; } - /** - * @param Preference $preference - * - * @return Collection - */ - public function getFrontpageAccounts(Preference $preference): Collection - { - $query = $this->user->accounts()->accountTypeIn(['Default account', 'Asset account']); - - if (count($preference->data) > 0) { - $query->whereIn('accounts.id', $preference->data); - } - - $result = $query->get(['accounts.*']); - - return $result; - } - - /** - * - * @param Account $account - * @param Carbon $start - * @param Carbon $end - * - * @return mixed - */ - public function getFrontpageTransactions(Account $account, Carbon $start, Carbon $end): Collection - { - $query = $this->user - ->transactionjournals() - ->expanded() - ->sortCorrectly() - ->before($end) - ->after($start) - ->take(10); - - // expand query: - $query->leftJoin( - 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id'); - } - )->where('source.account_id', $account->id); - $query->take(10); - $set = $query->get(TransactionJournal::queryFields()); - - return $set; - } - - /** - * Returns a list of transactions TO the given (asset) $account, but none from the - * given list of accounts - * - * @param Account $account - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getIncomeByDestination(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); - $journals = $this->user->transactionjournals() - ->expanded() - ->sortCorrectly() - ->before($end) - ->where('source_account.id', $account->id) - ->whereIn('destination_account.id', $ids) - ->after($start) - ->get(TransactionJournal::queryFields()); - - return $journals; - } - /** * @param Account $account * @param int $page @@ -435,7 +361,38 @@ class AccountRepository implements AccountRepositoryInterface } /** - * + * @param Collection $accounts + * @param array $types + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): Collection + { + // first collect actual transaction journals (fairly easy) + $query = $this->user->transactionjournals()->expanded()->sortCorrectly(); + + if ($end >= $start) { + $query->before($end)->after($start); + } + + if (count($types) > 0) { + $query->transactionTypes($types); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('t.account_id', $accountIds); + } + // that should do it: + $complete = $query->get(TransactionJournal::queryFields()); + + return $complete; + } + + /** + * * @param Account $account * @param Carbon $date * @@ -524,6 +481,22 @@ class AccountRepository implements AccountRepositoryInterface return $journal; } + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriod(Collection $accounts, Carbon $start, Carbon $end): string + { + Log::debug('spentinperiod'); + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $sum = $this->sumInPeriod($accounts, $types, $start, $end); + + return $sum; + } + /** * @param array $data * @@ -788,4 +761,64 @@ class AccountRepository implements AccountRepositoryInterface } } + + /** + * @param Collection $accounts + * @param array $types + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + private function sumInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): string + { + // first collect incoming transaction journals (where the $accounts receive the money). + $query = $this->user + ->transactionjournals() + ->distinct() + ->transactionTypes($types) + ->leftJoin( + 'transactions as t', function (JoinClause $join) { + $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '>', 0); + } + ); + + if ($end >= $start) { + $query->before($end)->after($start); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('t.account_id', $accountIds); + } + + // that should do it: + $first = strval($query->sum('t.amount')); + + // the the other way around: + $query = $this->user + ->transactionjournals() + ->distinct() + ->transactionTypes($types) + ->leftJoin( + 'transactions as t', function (JoinClause $join) { + $join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + } + ); + + if ($end >= $start) { + $query->before($end)->after($start); + } + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->whereIn('t.account_id', $accountIds); + } + + // that should do it: + $second = strval($query->sum('t.amount')); + $sum = bcadd($first, $second); + + Log::debug('SumInPeriodData ', ['accounts' => $accountIds, 'first' => $first, 'second' => $second, 'sum' => $sum]); + + return $sum; + } } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 3e83e6a121..24319d4232 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -6,7 +6,6 @@ namespace FireflyIII\Repositories\Account; use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountMeta; -use FireflyIII\Models\Preference; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use Illuminate\Pagination\LengthAwarePaginator; @@ -35,6 +34,15 @@ interface AccountRepositoryInterface */ public function destroy(Account $account, Account $moveTo): bool; + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriod(Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param int $accountId * @@ -42,6 +50,13 @@ interface AccountRepositoryInterface */ public function find(int $accountId): Account; + /** + * @param Account $account + * + * @return Carbon + */ + public function firstUseDate(Account $account): Carbon; + /** * Gets all the accounts by ID, for a given set. * @@ -51,38 +66,19 @@ interface AccountRepositoryInterface */ public function get(array $ids): Collection; + /** + * @param array $accountIds + * + * @return Collection + */ + public function getAccountsById(array $accountIds): Collection; + /** * @param array $types * * @return Collection */ - public function getAccounts(array $types): Collection; - - /** - * This method returns the users credit cards, along with some basic information about the - * balance they have on their CC. To be used in the JSON boxes on the front page that say - * how many bills there are still left to pay. The balance will be saved in field "balance". - * - * To get the balance, the field "date" is necessary. - * - * @param Carbon $date - * - * @return Collection - */ - public function getCreditCards(Carbon $date): Collection; - - /** - * Returns a list of transactions TO the given (expense) $account, all from the - * given list of accounts - * - * @param Account $account - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getExpensesByDestination(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection; + public function getAccountsByType(array $types): Collection; /** * @param TransactionJournal $journal @@ -93,35 +89,10 @@ interface AccountRepositoryInterface public function getFirstTransaction(TransactionJournal $journal, Account $account): Transaction; /** - * @param Preference $preference + * @deprecated * - * @return Collection - */ - public function getFrontpageAccounts(Preference $preference): Collection; - - /** - * @param Account $account - * @param Carbon $start - * @param Carbon $end + * SEE OTHER GETJOURNALS METHODS. * - * @return Collection - */ - public function getFrontpageTransactions(Account $account, Carbon $start, Carbon $end): Collection; - - /** - * Returns a list of transactions TO the given (asset) $account, but none from the - * given list of accounts - * - * @param Account $account - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getIncomeByDestination(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection; - - /** * @param Account $account * @param int $page * @param int $pageSize @@ -131,6 +102,10 @@ interface AccountRepositoryInterface public function getJournals(Account $account, int $page, int $pageSize = 50): LengthAwarePaginator; /** + * @deprecated + * + * SEE OTHER GETJOURNALS METHODS. + * * @param Account $account * @param Carbon $start * @param Carbon $end @@ -140,6 +115,8 @@ interface AccountRepositoryInterface public function getJournalsInRange(Account $account, Carbon $start, Carbon $end): Collection; /** + * @deprecated + * * Get the accounts of a user that have piggy banks connected to them. * * @return Collection @@ -147,6 +124,8 @@ interface AccountRepositoryInterface public function getPiggyBankAccounts(): Collection; /** + * @deprecated + * * Get savings accounts and the balance difference in the period. * * @return Collection @@ -154,6 +133,18 @@ interface AccountRepositoryInterface public function getSavingsAccounts() : Collection; /** + * @param Collection $accounts + * @param array $types + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): Collection; + + /** + * @deprecated + * * @param Account $account * @param Carbon $date * @@ -180,12 +171,23 @@ interface AccountRepositoryInterface public function oldestJournalDate(Account $account): Carbon; /** + * + * * @param Account $account * * @return TransactionJournal */ public function openingBalanceTransaction(Account $account) : TransactionJournal; + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriod(Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param array $data * @@ -203,6 +205,8 @@ interface AccountRepositoryInterface public function storeMeta(Account $account, string $name, $value): AccountMeta; /** + * @deprecated + * * @return string */ public function sumOfEverything() : string; diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index be4dd3c63d..60fb1ab575 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -250,55 +250,6 @@ class BillRepository implements BillRepositoryInterface return $amount; } - /** - * This method will tell you if you still have a CC bill to pay. Amount will be positive if the amount - * has been paid, otherwise it will be negative. - * - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function getCreditCardBill(Carbon $start, Carbon $end): string - { - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $amount = '0'; - $creditCards = $accountRepository->getCreditCards($end); // Find credit card accounts and possibly unpaid credit card bills. - /** @var Account $creditCard */ - foreach ($creditCards as $creditCard) { - if ($creditCard->balance == 0) { - // find a transfer TO the credit card which should account for anything paid. If not, the CC is not yet used. - $set = TransactionJournal::whereIn( - 'transaction_journals.id', function (Builder $q) use ($creditCard, $start, $end) { - $q->select('transaction_journals.id') - ->from('transactions') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transactions.account_id', $creditCard->id) - ->where('transactions.amount', '>', 0)// this makes the filter unnecessary. - ->where('transaction_journals.user_id', $this->user->id) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('transaction_types.type', TransactionType::TRANSFER); - } - )->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); - } - )->first([DB::raw('SUM(`transactions`.`amount`) as `sum_amount`')]); - $sumAmount = $set->sum_amount ?? '0'; - $amount = bcadd($amount, $sumAmount); - } else { - $amount = bcadd($amount, $creditCard->balance); - } - } - - return $amount; - - } - /** * This method also returns the amount of the journal in "journalAmount" * for easy access. diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index e1667fe83e..9563c7773c 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -84,17 +84,6 @@ interface BillRepositoryInterface */ public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string; - /** - * This method will tell you if you still have a CC bill to pay. Amount will be negative if the amount - * has been paid - * - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function getCreditCardBill(Carbon $start, Carbon $end): string; - /** * @param Bill $bill * diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index 3519b1b62f..14d6528135 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -108,9 +108,7 @@ class Navigation $currentEnd->$function(); } if (in_array($repeatFreq, $subDay)) { - Log::debug('Before subday: ' . $currentEnd->format('Y-m-d')); $currentEnd->subDay(); - Log::debug('After subday: ' . $currentEnd->format('Y-m-d')); } return $currentEnd; diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index 3e2152e25a..b623f6adaa 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -6,7 +6,7 @@ {% block content %}
-
+

{{ account.name }}

@@ -32,7 +32,7 @@
-
+

{{ 'transactions'|_ }}

@@ -42,6 +42,35 @@
+
+ {% for entry in entries %} + {% if entry[2] != 0 or entry[3] != 0 %} +
+ +
+ + {% if entry[2] != 0 %} + + + + + {% endif %} + {% if entry[3] != 0 %} + + + + + {% endif %} +
{{ 'spent'|_ }}{{ entry[2]|formatAmount }}
{{ 'earned'|_ }}{{ entry[3]|formatAmount }}
+
+
+ {% endif %} + + {% endfor %} +
diff --git a/resources/views/accounts/show_with_date.twig b/resources/views/accounts/show_with_date.twig new file mode 100644 index 0000000000..d3442b1b78 --- /dev/null +++ b/resources/views/accounts/show_with_date.twig @@ -0,0 +1,62 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, account) }} +{% endblock %} + +{% block content %} + +
+
+
+
+

{{ 'overview'|_ }} (period)

+ + + + +
+
+ +
+
+
+
+ +
+
+
+
+

{{ 'transactions'|_ }}

+
+
+ {% include 'list.journals' with {sorting:true} %} +
+
+
+
+ + + +{% endblock %} + +{% block scripts %} + + + + + + + +{% endblock %} From 3e36a29c2321ec3a463a18be4307ef199453816f Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 15:58:30 +0200 Subject: [PATCH 113/206] More refactoring. --- app/Console/Commands/VerifyDatabase.php | 5 +- app/Http/Controllers/HomeController.php | 6 - app/Http/Controllers/ReportController.php | 2 +- .../Account/AccountRepository.php | 127 +----------------- .../Account/AccountRepositoryInterface.php | 39 ------ 5 files changed, 6 insertions(+), 173 deletions(-) diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index ab8107bca2..ba53da1aa7 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -10,7 +10,6 @@ use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Illuminate\Console\Command; @@ -249,9 +248,7 @@ having transaction_count = 0 /** @var User $user */ foreach ($userRepository->all() as $user) { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class, [$user]); - $sum = $repository->sumOfEverything(); + $sum = $user->transactions()->sum('amount'); if (bccomp($sum, '0') !== 0) { $this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!'); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index a6fbd9f4a2..67a44d0142 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -127,12 +127,6 @@ class HomeController extends Controller $savingsTotal = bcadd($savingsTotal, Steam::balance($savingAccount, $end)); } - $sum = $repository->sumOfEverything(); - - if (bccomp($sum, '0') !== 0) { - Session::flash('error', strval(trans('firefly.unbalanced_error', ['amount' => Amount::format($sum, false)]))); - } - foreach ($accounts as $account) { $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); $set = $set->splice(0, 10); diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index e49f3e9487..6ff1bb3eb8 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -169,7 +169,7 @@ class ReportController extends Controller */ if ($start->between($first, $last) || $end->between($first, $last)) { $exists = true; - $journals = $repos->getJournalsInRange($account, $start, $end); + $journals = $repos->journalsInPeriod($accounts, [], $start, $end); } /* diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index f1d8292edc..6be17384db 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -12,13 +12,10 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Query\JoinClause; -use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Log; -use Session; use Steam; @@ -216,65 +213,6 @@ class AccountRepository implements AccountRepositoryInterface return $transaction; } - /** - * @param Account $account - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getJournals(Account $account, int $page, int $pageSize = 50): LengthAwarePaginator - { - $offset = ($page - 1) * $pageSize; - $query = $this->user - ->transactionJournals() - ->sortCorrectly() - ->expanded(); - - // expand query: - $query->leftJoin( - 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id'); - } - )->where('source.account_id', $account->id); - - - $count = $query->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); - $paginator = new LengthAwarePaginator($set, $count, $pageSize, $page); - - return $paginator; - - - } - - /** - * @param Account $account - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getJournalsInRange(Account $account, Carbon $start, Carbon $end): Collection - { - $query = $this->user - ->transactionJournals() - ->expanded() - ->sortCorrectly() - ->where( - function (Builder $q) use ($account) { - $q->where('destination_account.id', $account->id); - $q->orWhere('source_account.id', $account->id); - } - ) - ->after($start) - ->before($end); - - $set = $query->get(TransactionJournal::queryFields()); - - return $set; - } - /** * Get the accounts of a user that have piggy banks connected to them. * @@ -282,36 +220,14 @@ class AccountRepository implements AccountRepositoryInterface */ public function getPiggyBankAccounts(): Collection { - $start = clone Session::get('start', new Carbon); - $end = clone Session::get('end', new Carbon); $collection = new Collection(DB::table('piggy_banks')->distinct()->get(['piggy_banks.account_id'])); - $ids = $collection->pluck('account_id')->toArray(); + $accountIds = $collection->pluck('account_id')->toArray(); $accounts = new Collection; - - $ids = array_unique($ids); - if (count($ids) > 0) { - $accounts = $this->user->accounts()->whereIn('id', $ids)->where('accounts.active', 1)->get(); + $accountIds = array_unique($accountIds); + if (count($accountIds) > 0) { + $accounts = $this->user->accounts()->whereIn('id', $accountIds)->where('accounts.active', 1)->get(); } - $accounts->each( - function (Account $account) use ($start, $end) { - $account->startBalance = Steam::balanceIgnoreVirtual($account, $start); - $account->endBalance = Steam::balanceIgnoreVirtual($account, $end); - $account->piggyBalance = 0; - /** @var PiggyBank $piggyBank */ - foreach ($account->piggyBanks as $piggyBank) { - $account->piggyBalance += $piggyBank->currentRelevantRep()->currentamount; - } - // sum of piggy bank amounts on this account: - // diff between endBalance and piggyBalance. - // then, percentage. - $difference = bcsub($account->endBalance, $account->piggyBalance); - $account->difference = $difference; - $account->percentage = $difference != 0 && $account->endBalance != 0 ? round((($difference / $account->endBalance) * 100)) : 100; - - } - ); - return $accounts; } @@ -329,33 +245,6 @@ class AccountRepository implements AccountRepositoryInterface ->where('accounts.active', 1) ->where('account_meta.data', '"savingAsset"') ->get(['accounts.*']); - $start = clone Session::get('start', new Carbon); - $end = clone Session::get('end', new Carbon); - - $accounts->each( - function (Account $account) use ($start, $end) { - $account->startBalance = Steam::balance($account, $start); - $account->endBalance = Steam::balance($account, $end); - - // diff (negative when lost, positive when gained) - $diff = bcsub($account->endBalance, $account->startBalance); - - if ($diff < 0 && $account->startBalance > 0) { - // percentage lost compared to start. - $pct = (($diff * -1) / $account->startBalance) * 100; - } else { - if ($diff >= 0 && $account->startBalance > 0) { - $pct = ($diff / $account->startBalance) * 100; - } else { - $pct = 100; - } - } - $pct = $pct > 100 ? 100 : $pct; - $account->difference = $diff; - $account->percentage = round($pct); - - } - ); return $accounts; } @@ -543,14 +432,6 @@ class AccountRepository implements AccountRepositoryInterface return AccountMeta::create(['name' => $name, 'data' => $value, 'account_id' => $account->id,]); } - /** - * @return string - */ - public function sumOfEverything(): string - { - return strval($this->user->transactions()->sum('amount')); - } - /** * @param Account $account * @param array $data diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 24319d4232..3f48ae943e 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -89,34 +89,6 @@ interface AccountRepositoryInterface public function getFirstTransaction(TransactionJournal $journal, Account $account): Transaction; /** - * @deprecated - * - * SEE OTHER GETJOURNALS METHODS. - * - * @param Account $account - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getJournals(Account $account, int $page, int $pageSize = 50): LengthAwarePaginator; - - /** - * @deprecated - * - * SEE OTHER GETJOURNALS METHODS. - * - * @param Account $account - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getJournalsInRange(Account $account, Carbon $start, Carbon $end): Collection; - - /** - * @deprecated - * * Get the accounts of a user that have piggy banks connected to them. * * @return Collection @@ -124,8 +96,6 @@ interface AccountRepositoryInterface public function getPiggyBankAccounts(): Collection; /** - * @deprecated - * * Get savings accounts and the balance difference in the period. * * @return Collection @@ -143,7 +113,6 @@ interface AccountRepositoryInterface public function journalsInPeriod(Collection $accounts, array $types, Carbon $start, Carbon $end): Collection; /** - * @deprecated * * @param Account $account * @param Carbon $date @@ -171,7 +140,6 @@ interface AccountRepositoryInterface public function oldestJournalDate(Account $account): Carbon; /** - * * * @param Account $account * @@ -204,13 +172,6 @@ interface AccountRepositoryInterface */ public function storeMeta(Account $account, string $name, $value): AccountMeta; - /** - * @deprecated - * - * @return string - */ - public function sumOfEverything() : string; - /** * @param Account $account * @param array $data From 5166171e5d07088c0d9747f2fbeeb67be571673a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 17:22:24 +0200 Subject: [PATCH 114/206] More refactoring --- .../AccountChartGeneratorInterface.php | 6 +- .../Account/ChartJsAccountChartGenerator.php | 92 +------------- .../Controllers/Chart/AccountController.php | 120 ++++++++++++++---- app/Http/Controllers/HomeController.php | 10 +- app/Http/routes.php | 2 +- .../Account/AccountRepository.php | 58 ++++++++- .../Account/AccountRepositoryInterface.php | 20 ++- 7 files changed, 177 insertions(+), 131 deletions(-) diff --git a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php index 9e90645c5e..dd0c57c135 100644 --- a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php +++ b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php @@ -42,10 +42,10 @@ interface AccountChartGeneratorInterface /** * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param array $labels + * @param array $dataSet * * @return array */ - public function single(Account $account, Carbon $start, Carbon $end): array; + public function single(Account $account, array $labels, array $dataSet): array; } diff --git a/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php b/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php index 38987abec1..484a7aa517 100644 --- a/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php +++ b/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php @@ -29,28 +29,6 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface 'labels' => [], 'datasets' => [[ 'label' => trans('firefly.spent'), 'data' => []]]]; - - $start->subDay(); - $ids = $this->getIdsFromCollection($accounts); - $startBalances = Steam::balancesById($ids, $start); - $endBalances = Steam::balancesById($ids, $end); - - $accounts->each( - function (Account $account) use ($startBalances, $endBalances) { - $id = $account->id; - $startBalance = $this->isInArray($startBalances, $id); - $endBalance = $this->isInArray($endBalances, $id); - $diff = bcsub($endBalance, $startBalance); - $account->difference = round($diff, 2); - } - ); - - $accounts = $accounts->sortByDesc( - function (Account $account) { - return $account->difference; - } - ); - foreach ($accounts as $account) { if ($account->difference > 0) { $data['labels'][] = $account->name; @@ -80,7 +58,7 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface } foreach ($accounts as $account) { - $set = [ + $data['datasets'][] = [ 'label' => $account->name, 'fillColor' => 'rgba(220,220,220,0.2)', 'strokeColor' => 'rgba(220,220,220,1)', @@ -88,20 +66,8 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface 'pointStrokeColor' => '#fff', 'pointHighlightFill' => '#fff', 'pointHighlightStroke' => 'rgba(220,220,220,1)', - 'data' => [], + 'data' => $account->balances, ]; - $current = clone $start; - $range = Steam::balanceInRange($account, $start, clone $end); - $previous = round(array_values($range)[0], 2); - while ($current <= $end) { - $format = $current->format('Y-m-d'); - $balance = isset($range[$format]) ? round($range[$format], 2) : $previous; - - $set['data'][] = $balance; - $previous = $balance; - $current->addDay(); - } - $data['datasets'][] = $set; } $data['count'] = count($data['datasets']); @@ -110,71 +76,27 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface /** * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param array $labels + * @param array $dataSet * * @return array */ - public function single(Account $account, Carbon $start, Carbon $end): array + public function single(Account $account, array $labels, array $dataSet): array { // language: $format = (string)trans('config.month_and_day'); $data = [ 'count' => 1, - 'labels' => [], + 'labels' => $labels, 'datasets' => [ [ 'label' => $account->name, - 'data' => [], + 'data' => $dataSet, ], ], ]; - $range = Steam::balanceInRange($account, $start, $end); - $current = clone $start; - $previous = array_values($range)[0]; - - while ($end >= $current) { - $theDate = $current->format('Y-m-d'); - $balance = $range[$theDate] ?? $previous; - - $data['labels'][] = $current->formatLocalized($format); - $data['datasets'][0]['data'][] = $balance; - $previous = $balance; - $current->addDay(); - } - return $data; } - /** - * @param Collection $collection - * - * @return array - */ - protected function getIdsFromCollection(Collection $collection): array - { - $ids = []; - foreach ($collection as $entry) { - $ids[] = $entry->id; - } - - return array_unique($ids); - - } - - /** - * @param $array - * @param $entryId - * - * @return string - */ - protected function isInArray($array, $entryId): string - { - if (isset($array[$entryId])) { - return $array[$entryId]; - } - - return '0'; - } } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index e0689b1754..d06193e5ea 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -7,8 +7,13 @@ use Carbon\Carbon; use FireflyIII\Generator\Chart\Account\AccountChartGeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; +use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; +use Preferences; +use Response; +use Steam; /** checked * Class AccountController @@ -40,26 +45,44 @@ class AccountController extends Controller */ public function expenseAccounts(ARI $repository) { - /* - $start = clone session('start', Carbon::now()->startOfMonth()); - $end = clone session('end', Carbon::now()->endOfMonth()); - $accounts = $repository->getAccounts(['Expense account', 'Beneficiary account']); - - // chart properties for cache: - $cache = new CacheProperties(); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = clone session('end', Carbon::now()->endOfMonth()); + $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('expenseAccounts'); $cache->addProperty('accounts'); if ($cache->has()) { - return Response::json($cache->get()); + // return Response::json($cache->get()); } + $accounts = $repository->getAccountsByType(['Expense account', 'Beneficiary account']); + + $start->subDay(); + $ids = $accounts->pluck('id')->toArray(); + $startBalances = Steam::balancesById($ids, $start); + $endBalances = Steam::balancesById($ids, $end); + + $accounts->each( + function (Account $account) use ($startBalances, $endBalances) { + $id = $account->id; + $startBalance = $startBalances[$id] ?? '0'; + $endBalance = $endBalances[$id] ?? '0'; + $diff = bcsub($endBalance, $startBalance); + $account->difference = round($diff, 2); + } + ); + + + $accounts = $accounts->sortByDesc( + function (Account $account) { + return $account->difference; + } + ); $data = $this->generator->expenseAccounts($accounts, $start, $end); $cache->store($data); return Response::json($data); -*/ } /** @@ -71,42 +94,54 @@ class AccountController extends Controller */ public function frontpage(ARI $repository) { - /* - $frontPage = Preferences::get('frontPageAccounts', []); - $start = clone session('start', Carbon::now()->startOfMonth()); - $end = clone session('end', Carbon::now()->endOfMonth()); - $accounts = $repository->getFrontpageAccounts($frontPage); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = clone session('end', Carbon::now()->endOfMonth()); + // chart properties for cache: - $cache = new CacheProperties(); + $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('frontpage'); $cache->addProperty('accounts'); if ($cache->has()) { - return Response::json($cache->get()); + // return Response::json($cache->get()); } + $frontPage = Preferences::get('frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray()); + $accounts = $repository->getAccountsById($frontPage->data); + + foreach ($accounts as $account) { + $balances = []; + $current = clone $start; + $range = Steam::balanceInRange($account, $start, clone $end); + $previous = round(array_values($range)[0], 2); + while ($current <= $end) { + $format = $current->format('Y-m-d'); + $balance = isset($range[$format]) ? round($range[$format], 2) : $previous; + $previous = $balance; + $balances[] = $balance; + $current->addDay(); + } + $account->balances = $balances; + } $data = $this->generator->frontpage($accounts, $start, $end); $cache->store($data); return Response::json($data); -*/ } /** * Shows the balances for a given set of dates and accounts. * - * @param $reportType * @param Carbon $start * @param Carbon $end * @param Collection $accounts * * @return \Illuminate\Http\JsonResponse */ - public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function report(Carbon $start, Carbon $end, Collection $accounts) { - /* // chart properties for cache: $cache = new CacheProperties(); $cache->addProperty($start); @@ -114,10 +149,24 @@ class AccountController extends Controller $cache->addProperty('all'); $cache->addProperty('accounts'); $cache->addProperty('default'); - $cache->addProperty($reportType); $cache->addProperty($accounts); if ($cache->has()) { - return Response::json($cache->get()); + // return Response::json($cache->get()); + } + + foreach ($accounts as $account) { + $balances = []; + $current = clone $start; + $range = Steam::balanceInRange($account, $start, clone $end); + $previous = round(array_values($range)[0], 2); + while ($current <= $end) { + $format = $current->format('Y-m-d'); + $balance = isset($range[$format]) ? round($range[$format], 2) : $previous; + $previous = $balance; + $balances[] = $balance; + $current->addDay(); + } + $account->balances = $balances; } // make chart: @@ -125,7 +174,6 @@ class AccountController extends Controller $cache->store($data); return Response::json($data); - */ } /** @@ -137,8 +185,6 @@ class AccountController extends Controller */ public function single(Account $account) { - /* - $start = clone session('start', Carbon::now()->startOfMonth()); $end = clone session('end', Carbon::now()->endOfMonth()); @@ -150,13 +196,31 @@ class AccountController extends Controller $cache->addProperty('single'); $cache->addProperty($account->id); if ($cache->has()) { - return Response::json($cache->get()); + // return Response::json($cache->get()); } - $data = $this->generator->single($account, $start, $end); + $format = (string)trans('config.month_and_day'); + $range = Steam::balanceInRange($account, $start, $end); + $current = clone $start; + $previous = array_values($range)[0]; + $labels = []; + $chartData = []; + + while ($end >= $current) { + $theDate = $current->format('Y-m-d'); + $balance = $range[$theDate] ?? $previous; + + $labels[] = $current->formatLocalized($format); + $chartData[] = $balance; + $previous = $balance; + $current->addDay(); + } + + + $data = $this->generator->single($account, $labels, $chartData); $cache->store($data); return Response::json($data); - */ } + } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 67a44d0142..51577790b9 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -1,9 +1,9 @@ getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray() + ); /** @var Carbon $start */ $start = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ $end = session('end', Carbon::now()->endOfMonth()); $showTour = Preferences::get('tour', true)->data; $accounts = $repository->getAccountsById($frontPage->data); - $savings = $repository->getSavingsAccounts(); - $piggyBankAccounts = $repository->getPiggyBankAccounts(); + $savings = $repository->getSavingsAccounts($start, $end); + $piggyBankAccounts = $repository->getPiggyBankAccounts($start, $end); $savingsTotal = 0; diff --git a/app/Http/routes.php b/app/Http/routes.php index ed96303917..1555574eb9 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -191,7 +191,7 @@ Route::group( // accounts: Route::get('/chart/account/frontpage', ['uses' => 'Chart\AccountController@frontpage']); Route::get('/chart/account/expense', ['uses' => 'Chart\AccountController@expenseAccounts']); - Route::get('/chart/account/report/{reportType}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\AccountController@report']); + Route::get('/chart/account/report/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\AccountController@report']); Route::get('/chart/account/{account}', ['uses' => 'Chart\AccountController@single']); diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 6be17384db..ec420ba87f 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -216,9 +216,12 @@ class AccountRepository implements AccountRepositoryInterface /** * Get the accounts of a user that have piggy banks connected to them. * + * @param Carbon $start + * @param Carbon $end + * * @return Collection */ - public function getPiggyBankAccounts(): Collection + public function getPiggyBankAccounts(Carbon $start, Carbon $end): Collection { $collection = new Collection(DB::table('piggy_banks')->distinct()->get(['piggy_banks.account_id'])); $accountIds = $collection->pluck('account_id')->toArray(); @@ -228,16 +231,39 @@ class AccountRepository implements AccountRepositoryInterface $accounts = $this->user->accounts()->whereIn('id', $accountIds)->where('accounts.active', 1)->get(); } + $accounts->each( + function (Account $account) use ($start, $end) { + $account->startBalance = Steam::balanceIgnoreVirtual($account, $start); + $account->endBalance = Steam::balanceIgnoreVirtual($account, $end); + $account->piggyBalance = 0; + /** @var PiggyBank $piggyBank */ + foreach ($account->piggyBanks as $piggyBank) { + $account->piggyBalance += $piggyBank->currentRelevantRep()->currentamount; + } + // sum of piggy bank amounts on this account: + // diff between endBalance and piggyBalance. + // then, percentage. + $difference = bcsub($account->endBalance, $account->piggyBalance); + $account->difference = $difference; + $account->percentage = $difference != 0 && $account->endBalance != 0 ? round((($difference / $account->endBalance) * 100)) : 100; + + } + ); + + return $accounts; } /** - * Get savings accounts and the balance difference in the period. + * Get savings accounts. + * + * @param Carbon $start + * @param Carbon $end * * @return Collection */ - public function getSavingsAccounts(): Collection + public function getSavingsAccounts(Carbon $start, Carbon $end): Collection { $accounts = $this->user->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC') ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') @@ -246,6 +272,32 @@ class AccountRepository implements AccountRepositoryInterface ->where('account_meta.data', '"savingAsset"') ->get(['accounts.*']); + $accounts->each( + function (Account $account) use ($start, $end) { + $account->startBalance = Steam::balance($account, $start); + $account->endBalance = Steam::balance($account, $end); + + // diff (negative when lost, positive when gained) + $diff = bcsub($account->endBalance, $account->startBalance); + + if ($diff < 0 && $account->startBalance > 0) { + // percentage lost compared to start. + $pct = (($diff * -1) / $account->startBalance) * 100; + } else { + if ($diff >= 0 && $account->startBalance > 0) { + $pct = ($diff / $account->startBalance) * 100; + } else { + $pct = 100; + } + } + $pct = $pct > 100 ? 100 : $pct; + $account->difference = $diff; + $account->percentage = round($pct); + + } + ); + + return $accounts; } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 3f48ae943e..4949bed888 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -91,16 +91,22 @@ interface AccountRepositoryInterface /** * Get the accounts of a user that have piggy banks connected to them. * - * @return Collection - */ - public function getPiggyBankAccounts(): Collection; - - /** - * Get savings accounts and the balance difference in the period. + * @param Carbon $start + * @param Carbon $end * * @return Collection */ - public function getSavingsAccounts() : Collection; + public function getPiggyBankAccounts(Carbon $start, Carbon $end): Collection; + + /** + * Get savings accounts. + * + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getSavingsAccounts(Carbon $start, Carbon $end): Collection; /** * @param Collection $accounts From 5a6967cefd02eb4a1184d0acae9ea659aa179e59 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 13 May 2016 19:40:13 +0200 Subject: [PATCH 115/206] Better formatting for split transactions. --- app/Http/Controllers/ReportController.php | 4 +- .../Account/AccountRepository.php | 18 ++++----- app/Support/Twig/Journal.php | 24 ++++++++++++ resources/views/accounts/show.twig | 2 +- resources/views/list/journals.twig | 6 ++- resources/views/reports/index.twig | 39 +++++++++++++++++++ 6 files changed, 77 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 6ff1bb3eb8..24e318e979 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -163,12 +163,11 @@ class ReportController extends Controller $exists = false; $journals = new Collection; $dayBeforeBalance = Steam::balance($account, $dayBefore); - /* * Is there even activity on this account between the requested dates? */ if ($start->between($first, $last) || $end->between($first, $last)) { - $exists = true; + $exists = true; $journals = $repos->journalsInPeriod($accounts, [], $start, $end); } @@ -203,7 +202,6 @@ class ReportController extends Controller $auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance; } - $reportType = 'audit'; $accountIds = join(',', $accounts->pluck('id')->toArray()); diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index ec420ba87f..169b3fcae1 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -368,13 +368,10 @@ class AccountRepository implements AccountRepositoryInterface ->sortCorrectly() ->first(['transaction_journals.*']); if (is_null($journal)) { - $date = new Carbon; - $date->addYear(); // in the future. - } else { - $date = $journal->date; + return new Carbon('1900-01-01'); } - return $date; + return $journal->date; } /** @@ -390,16 +387,15 @@ class AccountRepository implements AccountRepositoryInterface $journal = TransactionJournal:: leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.account_id', $account->id) - ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.date', 'ASC') + ->orderBy('transaction_journals.order', 'DESC') + ->orderBy('transaction_journals.id', 'Ã…SC') ->first(['transaction_journals.*']); if (is_null($journal)) { - $date = new Carbon; - $date->addYear(); // in the future. - } else { - $date = $journal->date; + return new Carbon('1900-01-01'); } - return $date; + return $journal->date; } /** diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index ff505f0e12..262c5be668 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -4,6 +4,7 @@ declare(strict_types = 1); namespace FireflyIII\Support\Twig; +use Amount; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; use FireflyIII\Support\CacheProperties; @@ -18,6 +19,28 @@ use Twig_SimpleFunction; */ class Journal extends Twig_Extension { + /** + * @return Twig_SimpleFunction + */ + public function formatPerspective(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatPerspective', function (TransactionJournal $journal, Account $account) { + + // get the account amount: + $transaction = $journal->transactions()->where('transactions.account_id', $account->id)->first(); + $amount = $transaction->amount; + if ($journal->isWithdrawal()) { + $amount = bcmul($amount, '-1'); + } + + $formatted = Amount::format($amount, true); + + return $formatted . ' (' . Amount::formatJournal($journal) . ')'; + } + ); + } + /** * @return Twig_SimpleFunction */ @@ -71,6 +94,7 @@ class Journal extends Twig_Extension $functions = [ $this->getSourceAccount(), $this->getDestinationAccount(), + $this->formatPerspective(), ]; return $functions; diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index b623f6adaa..f6456825d1 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -38,7 +38,7 @@

{{ 'transactions'|_ }}

- {% include 'list.journals' with {sorting:true} %} + {% include 'list.journals' with {sorting:true, accountPerspective: account} %}
diff --git a/resources/views/list/journals.twig b/resources/views/list/journals.twig index 447a189fa5..a57becf748 100644 --- a/resources/views/list/journals.twig +++ b/resources/views/list/journals.twig @@ -57,7 +57,11 @@ - {{ journal|formatJournal }} + {% if not accountPerspective %} + {{ journal|formatJournal }} + {% else %} + {{ formatPerspective(journal, accountPerspective)|raw }} + {% endif %} {{ journal.date.formatLocalized(monthAndDayFormat) }} diff --git a/resources/views/reports/index.twig b/resources/views/reports/index.twig index bab746d9ff..62ef34f336 100644 --- a/resources/views/reports/index.twig +++ b/resources/views/reports/index.twig @@ -135,6 +135,45 @@ ]) }}">{{ 'report_all_time_quick'|_ }} + +

{{ 'quick_link_audit_report'|_ }}

+ +

{{ 'reports_can_bookmark'|_ }}

From 863227c55cdd0dae7276f2de00432d6949ce41d3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 13:51:33 +0200 Subject: [PATCH 116/206] Some refactoring. --- app/Helpers/Collection/Expense.php | 40 ++-- app/Helpers/Collection/Income.php | 33 +-- app/Helpers/Report/ReportHelper.php | 23 ++- app/Helpers/Report/ReportQuery.php | 70 ------- app/Helpers/Report/ReportQueryInterface.php | 24 --- app/Http/Controllers/JsonController.php | 23 +-- .../Controllers/TransactionController.php | 46 ++++- app/Support/Migration/TestData.php | 4 +- .../Models/TransactionJournalSupport.php | 41 +++- database/seeds/SplitDataSeeder.php | 190 ++++++++---------- .../views/reports/partials/expenses.twig | 4 +- storage/database/.gitignore | 2 + 12 files changed, 240 insertions(+), 260 deletions(-) diff --git a/app/Helpers/Collection/Expense.php b/app/Helpers/Collection/Expense.php index c963b631e3..5e397db9a1 100644 --- a/app/Helpers/Collection/Expense.php +++ b/app/Helpers/Collection/Expense.php @@ -2,7 +2,6 @@ declare(strict_types = 1); namespace FireflyIII\Helpers\Collection; -use Crypt; use FireflyIII\Models\TransactionJournal; use Illuminate\Support\Collection; use stdClass; @@ -33,25 +32,32 @@ class Expense */ public function addOrCreateExpense(TransactionJournal $entry) { - $accountId = $entry->account_id; - $amount = strval(round($entry->journalAmount, 2)); - if (bccomp('0', $amount) === -1) { - $amount = bcmul($amount, '-1'); + // add each account individually: + $destinations = TransactionJournal::destinationTransactionList($entry); + + foreach ($destinations as $transaction) { + $amount = strval($transaction->amount); + $account = $transaction->account; + if (bccomp('0', $amount) === -1) { + $amount = bcmul($amount, '-1'); + } + + $object = new stdClass; + $object->amount = $amount; + $object->name = $account->name; + $object->count = 1; + $object->id = $account->id; + + // overrule some properties: + if ($this->expenses->has($account->id)) { + $object = $this->expenses->get($account->id); + $object->amount = bcadd($object->amount, $amount); + $object->count++; + } + $this->expenses->put($account->id, $object); } - $object = new stdClass; - $object->amount = $amount; - $object->name = Crypt::decrypt($entry->account_name); - $object->count = 1; - $object->id = $accountId; - // overrule some properties: - if ($this->expenses->has($accountId)) { - $object = $this->expenses->get($accountId); - $object->amount = bcadd($object->amount, $amount); - $object->count++; - } - $this->expenses->put($accountId, $object); } /** diff --git a/app/Helpers/Collection/Income.php b/app/Helpers/Collection/Income.php index a618a5a058..a879f34a20 100644 --- a/app/Helpers/Collection/Income.php +++ b/app/Helpers/Collection/Income.php @@ -2,7 +2,6 @@ declare(strict_types = 1); namespace FireflyIII\Helpers\Collection; -use Crypt; use FireflyIII\Models\TransactionJournal; use Illuminate\Support\Collection; use stdClass; @@ -34,21 +33,29 @@ class Income */ public function addOrCreateIncome(TransactionJournal $entry) { - $accountId = $entry->account_id; + // add each account individually: + $sources = TransactionJournal::sourceTransactionList($entry); - $object = new stdClass; - $object->amount = strval(round($entry->journalAmount, 2)); - $object->name = Crypt::decrypt($entry->account_name); - $object->count = 1; - $object->id = $accountId; + foreach ($sources as $transaction) { + $amount = strval($transaction->amount); + $account = $transaction->account; + $amount = bcmul($amount, '-1'); - // overrule some properties: - if ($this->incomes->has($accountId)) { - $object = $this->incomes->get($accountId); - $object->amount = bcadd($object->amount, $entry->journalAmount); - $object->count++; + $object = new stdClass; + $object->amount = $amount; + $object->name = $account->name; + $object->count = 1; + $object->id = $account->id; + + // overrule some properties: + if ($this->incomes->has($account->id)) { + $object = $this->incomes->get($account->id); + $object->amount = bcadd($object->amount, $amount); + $object->count++; + } + $this->incomes->put($account->id, $object); } - $this->incomes->put($accountId, $object); + } /** diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index ec79046336..98e3c3fa14 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -14,6 +14,8 @@ use FireflyIII\Models\Bill; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -171,10 +173,15 @@ class ReportHelper implements ReportHelperInterface public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): Expense { $object = new Expense; - $set = $this->query->expense($accounts, $start, $end); + /** @var AccountRepositoryInterface $repos */ + $repos = app(AccountRepositoryInterface::class); + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + $journals = $repos->journalsInPeriod($accounts, $types, $start, $end); - foreach ($set as $entry) { - $object->addToTotal($entry->journalAmount); // can be positive, if it's a transfer + /** @var TransactionJournal $entry */ + foreach ($journals as $entry) { + $amount = TransactionJournal::amount($entry); + $object->addToTotal($amount); $object->addOrCreateExpense($entry); } @@ -193,10 +200,14 @@ class ReportHelper implements ReportHelperInterface public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): Income { $object = new Income; - $set = $this->query->income($accounts, $start, $end); + /** @var AccountRepositoryInterface $repos */ + $repos = app(AccountRepositoryInterface::class); + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + $journals = $repos->journalsInPeriod($accounts, $types, $start, $end); - foreach ($set as $entry) { - $object->addToTotal($entry->journalAmount); + foreach ($journals as $entry) { + $amount = TransactionJournal::amount($entry); + $object->addToTotal($amount); $object->addOrCreateIncome($entry); } diff --git a/app/Helpers/Report/ReportQuery.php b/app/Helpers/Report/ReportQuery.php index dd451834b9..365c66e171 100644 --- a/app/Helpers/Report/ReportQuery.php +++ b/app/Helpers/Report/ReportQuery.php @@ -62,76 +62,6 @@ class ReportQuery implements ReportQueryInterface return $array; } - /** - * This method returns all the "out" transaction journals for the given account and given period. The amount - * is stored in "journalAmount". - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function expense(Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); - $set = Auth::user()->transactionjournals() - ->leftJoin( - 'transactions as t_from', function (JoinClause $join) { - $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions as t_to', function (JoinClause $join) { - $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0); - } - ) - ->leftJoin('accounts', 't_to.account_id', '=', 'accounts.id') - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->before($end) - ->after($start) - ->whereIn('t_from.account_id', $ids) - ->whereNotIn('t_to.account_id', $ids) - ->get(['transaction_journals.*', 't_from.amount as journalAmount', 'accounts.id as account_id', 'accounts.name as account_name']); - - return $set; - } - - /** - * This method returns all the "in" transaction journals for the given account and given period. The amount - * is stored in "journalAmount". - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function income(Collection $accounts, Carbon $start, Carbon $end): Collection - { - $ids = $accounts->pluck('id')->toArray(); - $set = Auth::user()->transactionjournals() - ->leftJoin( - 'transactions as t_from', function (JoinClause $join) { - $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions as t_to', function (JoinClause $join) { - $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0); - } - ) - ->leftJoin('accounts', 't_from.account_id', '=', 'accounts.id') - ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->before($end) - ->after($start) - ->whereIn('t_to.account_id', $ids) - ->whereNotIn('t_from.account_id', $ids) - ->get(['transaction_journals.*', 't_to.amount as journalAmount', 'accounts.id as account_id', 'accounts.name as account_name']); - - return $set; - } - /** * Returns an array of the amount of money spent in the given accounts (on withdrawals, opening balances and transfers) * grouped by month like so: "2015-01" => '123.45' diff --git a/app/Helpers/Report/ReportQueryInterface.php b/app/Helpers/Report/ReportQueryInterface.php index f746710ffb..4dba7f3d5d 100644 --- a/app/Helpers/Report/ReportQueryInterface.php +++ b/app/Helpers/Report/ReportQueryInterface.php @@ -26,30 +26,6 @@ interface ReportQueryInterface */ public function earnedPerMonth(Collection $accounts, Carbon $start, Carbon $end): array; - /** - * This method returns all the "out" transaction journals for the given account and given period. The amount - * is stored in "journalAmount". - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function expense(Collection $accounts, Carbon $start, Carbon $end): Collection; - - /** - * This method returns all the "in" transaction journals for the given account and given period. The amount - * is stored in "journalAmount". - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function income(Collection $accounts, Carbon $start, Carbon $end): Collection; - /** * Returns an array of the amount of money spent in the given accounts (on withdrawals, opening balances and transfers) * grouped by month like so: "2015-01" => '123.45' diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 5ae73299b3..d2003290fb 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -3,7 +3,6 @@ use Amount; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Report\ReportQueryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; @@ -84,13 +83,11 @@ class JsonController extends Controller } /** - * @param ReportQueryInterface $reportQuery - * - * @param ARI $accountRepository + * @param ARI $accountRepository * * @return \Symfony\Component\HttpFoundation\Response */ - public function boxIn(ReportQueryInterface $reportQuery, ARI $accountRepository) + public function boxIn(ARI $accountRepository) { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); @@ -104,28 +101,23 @@ class JsonController extends Controller return Response::json($cache->get()); } $accounts = $accountRepository->getAccountsByType(['Default account', 'Asset account', 'Cash account']); - $amount = $reportQuery->income($accounts, $start, $end)->sum('journalAmount'); - - $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + $amount = $accountRepository->earnedInPeriod($accounts, $start, $end); + $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $cache->store($data); return Response::json($data); } /** - * @param ReportQueryInterface $reportQuery - * - * @param ARI $accountRepository + * @param ARI $accountRepository * * @return \Symfony\Component\HttpFoundation\Response */ - public function boxOut(ReportQueryInterface $reportQuery, ARI $accountRepository) + public function boxOut(ARI $accountRepository) { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); - $accounts = $accountRepository->getAccountsByType(['Default account', 'Asset account', 'Cash account']); - // works for json too! $cache = new CacheProperties; $cache->addProperty($start); @@ -135,7 +127,8 @@ class JsonController extends Controller return Response::json($cache->get()); } - $amount = $reportQuery->expense($accounts, $start, $end)->sum('journalAmount'); + $accounts = $accountRepository->getAccountsByType(['Default account', 'Asset account', 'Cash account']); + $amount = $accountRepository->spentInPeriod($accounts, $start, $end); $data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; $cache->store($data); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index ee79095b42..5264304cb9 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -16,6 +16,7 @@ use DB; use ExpandedForm; use FireflyIII\Events\TransactionJournalStored; use FireflyIII\Events\TransactionJournalUpdated; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Requests\JournalFormRequest; use FireflyIII\Http\Requests\MassDeleteJournalRequest; @@ -420,11 +421,46 @@ class TransactionController extends Controller } ); - // TODO different for each transaction type! - /** @var Collection $transactions */ - $transactions = $journal->transactions()->groupBy('transactions.account_id')->orderBy('amount', 'ASC')->get( - ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] - ); + switch ($journal->transactionType->type) { + case TransactionType::DEPOSIT: + /** @var Collection $transactions */ + $transactions = $journal->transactions() + ->groupBy('transactions.account_id') + ->where('amount', '<', 0) + ->orderBy('amount', 'ASC')->get( + ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] + ); + $final = $journal->transactions() + ->groupBy('transactions.account_id') + ->where('amount', '>', 0) + ->orderBy('amount', 'ASC')->first( + ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] + ); + $final->description = ''; + $transactions->push($final); + break; + case TransactionType::WITHDRAWAL: + /** @var Collection $transactions */ + $transactions = $journal->transactions() + ->groupBy('transactions.account_id') + ->where('amount', '>', 0) + ->orderBy('amount', 'ASC')->get( + ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] + ); + $final = $journal->transactions() + ->groupBy('transactions.account_id') + ->where('amount', '<', 0) + ->orderBy('amount', 'ASC')->first( + ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] + ); + $final->description = ''; + $transactions->push($final); + break; + default: + throw new FireflyException('Cannot handle ' . $journal->transactionType->type); + break; + } + // foreach do balance thing $transactions->each( diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index 9a921e704d..5e6e0a08b5 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -316,6 +316,8 @@ class TestData { Category::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]); Category::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); + Category::firstOrCreateEncrypted(['name' => 'Reimbursements', 'user_id' => $user->id]); + Category::firstOrCreateEncrypted(['name' => 'Salary', 'user_id' => $user->id]); return true; } @@ -690,7 +692,7 @@ class TestData */ public static function createRevenueAccounts(User $user): bool { - $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google']; + $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google', 'Work SixtyFive', 'Work EightyFour','Work Fiftyone']; foreach ($revenues as $name) { // create account: Account::create( diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index bc49cc3d1e..b83bec79b5 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -279,7 +279,6 @@ class TransactionJournalSupport extends Model return $account; } - /** * @param TransactionJournal $journal * @@ -327,6 +326,46 @@ class TransactionJournalSupport extends Model return $type; } + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public static function sourceTransactionList(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('source-transaction-list'); + if ($cache->has()) { + return $cache->get(); + } + $list = $journal->transactions()->where('amount', '<', 0)->with('account')->get(); + $cache->store($list); + + return $list; + } + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public static function destinationTransactionList(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-transaction-list'); + if ($cache->has()) { + return $cache->get(); + } + $list = $journal->transactions()->where('amount', '>', 0)->with('account')->get(); + $cache->store($list); + + return $list; + } + /** * @param TransactionJournal $journal * diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php index 826290d40b..1134bc6097 100644 --- a/database/seeds/SplitDataSeeder.php +++ b/database/seeds/SplitDataSeeder.php @@ -43,7 +43,7 @@ class SplitDataSeeder extends Seeder public function run() { $skipWithdrawal = false; - $skipDeposit = true; + $skipDeposit = false; $skipTransfer = true; // start by creating all users: // method will return the first user. @@ -80,7 +80,7 @@ class SplitDataSeeder extends Seeder $today->addDay(); if (!$skipDeposit) { - $this->generateDeposits(); + $this->generateDeposits($user); } // create a splitted transfer of 57,- (19) // $today->addDay(); @@ -90,51 +90,66 @@ class SplitDataSeeder extends Seeder } } - private function generateDeposits() + /** + * @param User $user + */ + private function generateDeposits(User $user) { - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 2, // expense - 'transaction_currency_id' => 1, - 'description' => 'Split Income (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] + /* + * DEPOSIT ONE + */ + $sources = ['Work SixtyFive', 'Work EightyFour']; + $categories = ['Salary', 'Reimbursements']; + $amounts = [50, 50]; + $destination = TestData::findAccount($user, 'Alternate Checking Account'); + $date = new Carbon('2012-03-15'); + $journal = TransactionJournal::create( + ['user_id' => $user->id, 'transaction_type_id' => 2, 'transaction_currency_id' => 1, 'description' => 'Split Even Income (journal (50/50))', + 'completed' => 1, 'date' => $date->format('Y-m-d'),] ); - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. - $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; - $source = TestData::findAccount($user, 'Belastingdienst'); - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-33', - - ] + foreach ($sources as $index => $source) { + $cat = $categories[$index]; + $source = TestData::findAccount($user, $source); + $one = Transaction::create( + ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, + 'description' => 'Split Even Income #' . $index,] + ); + $two = Transaction::create( + ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], + 'description' => 'Split Even Income #' . $index,] ); - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '33', + $one->categories()->save(TestData::findCategory($user, $cat)); + $two->categories()->save(TestData::findCategory($user, $cat)); + } - ] + /* + * DEPOSIT TWO. + */ + + + $sources = ['Work SixtyFive', 'Work EightyFour', 'Work Fiftyone']; + $categories = ['Salary', 'Bills', 'Reimbursements']; + $amounts = [15, 34, 51]; + $destination = TestData::findAccount($user, 'Checking Account'); + $date = new Carbon; + $journal = TransactionJournal::create( + ['user_id' => $user->id, 'transaction_type_id' => 2, 'transaction_currency_id' => 1, + 'description' => 'Split Uneven Income (journal (15/34/51=100))', 'completed' => 1, 'date' => $date->format('Y-m-d'),] + ); + + foreach ($sources as $index => $source) { + $cat = $categories[$index]; + $source = TestData::findAccount($user, $source); + $one = Transaction::create( + ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, + 'description' => 'Split Uneven Income #' . $index,] + ); + $two = Transaction::create( + ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], + 'description' => 'Split Uneven Income #' . $index,] ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); $one->categories()->save(TestData::findCategory($user, $cat)); $two->categories()->save(TestData::findCategory($user, $cat)); @@ -190,114 +205,75 @@ class SplitDataSeeder extends Seeder } } + /** + * @param User $user + */ private function generateWithdrawals(User $user) { /* * TRANSACTION ONE */ - $date = new Carbon('2012-03-15'); - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 1, // withdrawal - 'transaction_currency_id' => 1, - 'description' => 'Split Even Expense (journal (50/50))', - 'completed' => 1, - 'date' => $date->format('Y-m-d'), - ] - ); - - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. $destinations = ['SixtyFive', 'EightyFour']; $budgets = ['Groceries', 'Car']; $categories = ['Bills', 'Bills']; $amounts = [50, 50]; $source = TestData::findAccount($user, 'Alternate Checking Account'); + $date = new Carbon('2012-03-15'); + $journal = TransactionJournal::create( + ['user_id' => $user->id, 'transaction_type_id' => 1, 'transaction_currency_id' => 1, 'description' => 'Split Even Expense (journal (50/50))', + 'completed' => 1, 'date' => $date->format('Y-m-d'),] + ); + foreach ($destinations as $index => $dest) { $bud = $budgets[$index]; $cat = $categories[$index]; $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amounts[$index] * -1, - 'description' => 'Split Even Expense #' . $index, - - ] + $one = Transaction::create( + ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, + 'description' => 'Split Even Expense #' . $index,] ); - - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amounts[$index], - 'description' => 'Split Even Expense #' . $index, - - ] + $two = Transaction::create( + ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], + 'description' => 'Split Even Expense #' . $index,] ); $one->budgets()->save(TestData::findBudget($user, $bud)); $two->budgets()->save(TestData::findBudget($user, $bud)); - $one->categories()->save(TestData::findCategory($user, $cat)); $two->categories()->save(TestData::findCategory($user, $cat)); } /* - * GENERATE TRANSACTION TWO. + * TRANSACTION TWO. */ - $date = new Carbon; - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 1, // withdrawal - 'transaction_currency_id' => 1, - 'description' => 'Split Uneven Expense (journal (15/34/51=100))', - 'completed' => 1, - 'date' => $date->format('Y-m-d'), - ] - ); - // split in 6 transactions (multiple destinations). 22,- each - // source is TestData Checking Account. - // also attach some budgets and stuff. $destinations = ['SixtyFive', 'EightyFour', 'Fiftyone']; $budgets = ['Groceries', 'Groceries', 'Car']; $categories = ['Bills', 'Bills', 'Car']; $amounts = [15, 34, 51]; $source = TestData::findAccount($user, 'Checking Account'); + $date = new Carbon; + $journal = TransactionJournal::create( + ['user_id' => $user->id, 'transaction_type_id' => 1, 'transaction_currency_id' => 1, + 'description' => 'Split Uneven Expense (journal (15/34/51=100))', 'completed' => 1, 'date' => $date->format('Y-m-d'),] + ); + foreach ($destinations as $index => $dest) { $bud = $budgets[$index]; $cat = $categories[$index]; $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amounts[$index] * -1, - 'description' => 'Split Uneven Expense #' . $index, - - ] + $one = Transaction::create( + ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, + 'description' => 'Split Uneven Expense #' . $index,] ); - - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amounts[$index], - 'description' => 'Split Uneven Expense #' . $index, - ] + $two = Transaction::create( + ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], + 'description' => 'Split Uneven Expense #' . $index,] ); $one->budgets()->save(TestData::findBudget($user, $bud)); $two->budgets()->save(TestData::findBudget($user, $bud)); - $one->categories()->save(TestData::findCategory($user, $cat)); $two->categories()->save(TestData::findCategory($user, $cat)); } diff --git a/resources/views/reports/partials/expenses.twig b/resources/views/reports/partials/expenses.twig index 34173d3339..83cb655eba 100644 --- a/resources/views/reports/partials/expenses.twig +++ b/resources/views/reports/partials/expenses.twig @@ -21,7 +21,9 @@ {% endif %} - {{ (expense.amount)|formatAmount }} + + {{ (expense.amount)|formatAmount }} + {% endfor %} diff --git a/storage/database/.gitignore b/storage/database/.gitignore index d6b7ef32c8..79a5312b13 100644 --- a/storage/database/.gitignore +++ b/storage/database/.gitignore @@ -1,2 +1,4 @@ * !.gitignore +!seed.local.json +!seed.split.json \ No newline at end of file From 6090efe2df5c057cab0f4cbd61915b77057d76c8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 14:02:12 +0200 Subject: [PATCH 117/206] Refactoring. --- .../Controllers/Chart/ReportController.php | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index d6eae001b8..c6c24886dc 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -6,10 +6,11 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface; -use FireflyIII\Helpers\Report\ReportQueryInterface; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; +use Navigation; use Response; use Steam; @@ -81,18 +82,15 @@ class ReportController extends Controller /** - * Summarizes all income and expenses, per month, for a given year. - * - * @param ReportQueryInterface $query - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * + * @param AccountRepositoryInterface $repository + * @param string $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return \Illuminate\Http\JsonResponse */ - public function yearInOut(ReportQueryInterface $query, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function yearInOut(AccountRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { // chart properties for cache: $cache = new CacheProperties; @@ -102,13 +100,22 @@ class ReportController extends Controller $cache->addProperty($accounts); $cache->addProperty($end); if ($cache->has()) { - return Response::json($cache->get()); + // return Response::json($cache->get()); } - // spent per month, and earned per month. For a specific set of accounts - // grouped by month - $spentArray = $query->spentPerMonth($accounts, $start, $end); - $earnedArray = $query->earnedPerMonth($accounts, $start, $end); + // always per month. + $currentStart = clone $start; + $spentArray = []; + $earnedArray = []; + while ($currentStart <= $end) { + $currentEnd = Navigation::endOfPeriod($currentStart, '1M'); + $date = $currentStart->format('Y-m'); + $spent = $repository->spentInPeriod($accounts, $currentStart, $currentEnd); + $earned = $repository->earnedInPeriod($accounts, $currentStart, $currentEnd); + $spentArray[$date] = $spent; + $earnedArray[$date] = $earned; + $currentStart = Navigation::addPeriod($currentStart, '1M', 0); + } if ($start->diffInMonths($end) > 12) { // data = method X @@ -125,17 +132,15 @@ class ReportController extends Controller } /** - * Summarizes all income and expenses for a given year. Gives a total and an average. - * - * @param ReportQueryInterface $query - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts + * @param AccountRepositoryInterface $repository + * @param string $reportType + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts * * @return \Illuminate\Http\JsonResponse */ - public function yearInOutSummarized(ReportQueryInterface $query, string $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function yearInOutSummarized(AccountRepositoryInterface $repository, string $reportType, Carbon $start, Carbon $end, Collection $accounts) { // chart properties for cache: @@ -148,10 +153,22 @@ class ReportController extends Controller if ($cache->has()) { return Response::json($cache->get()); } - // spent per month, and earned per month. For a specific set of accounts - // grouped by month - $spentArray = $query->spentPerMonth($accounts, $start, $end); - $earnedArray = $query->earnedPerMonth($accounts, $start, $end); + + // always per month. + $currentStart = clone $start; + $spentArray = []; + $earnedArray = []; + while ($currentStart <= $end) { + $currentEnd = Navigation::endOfPeriod($currentStart, '1M'); + $date = $currentStart->format('Y-m'); + $spent = $repository->spentInPeriod($accounts, $currentStart, $currentEnd); + $earned = $repository->earnedInPeriod($accounts, $currentStart, $currentEnd); + $spentArray[$date] = $spent; + $earnedArray[$date] = $earned; + $currentStart = Navigation::addPeriod($currentStart, '1M', 0); + } + + if ($start->diffInMonths($end) > 12) { // per year $data = $this->multiYearInOutSummarized($earnedArray, $spentArray, $start, $end); @@ -253,8 +270,8 @@ class ReportController extends Controller while ($start < $end) { // total income and total expenses: $date = $start->format('Y-m'); - $incomeSum = $earned[$date] ?? 0; - $expenseSum = isset($spent[$date]) ? ($spent[$date] * -1) : 0; + $incomeSum = isset($earned[$date]) ? $earned[$date] * -1 : 0; + $expenseSum = isset($spent[$date]) ? $spent[$date] * -1 : 0; $entries->push([clone $start, $incomeSum, $expenseSum]); $start->addMonth(); @@ -281,7 +298,7 @@ class ReportController extends Controller while ($start < $end) { $date = $start->format('Y-m'); $currentIncome = $earned[$date] ?? '0'; - $currentExpense = isset($spent[$date]) ? bcmul($spent[$date], '-1') : '0'; + $currentExpense = $spent[$date] ??'0'; $income = bcadd($income, $currentIncome); $expense = bcadd($expense, $currentExpense); From 771926c7793fd3fe3f810d3833e1341cdaa3eb71 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 14:02:37 +0200 Subject: [PATCH 118/206] No longer needed. --- app/Helpers/Report/ReportQuery.php | 109 -------------------- app/Helpers/Report/ReportQueryInterface.php | 42 -------- 2 files changed, 151 deletions(-) delete mode 100644 app/Helpers/Report/ReportQuery.php delete mode 100644 app/Helpers/Report/ReportQueryInterface.php diff --git a/app/Helpers/Report/ReportQuery.php b/app/Helpers/Report/ReportQuery.php deleted file mode 100644 index 365c66e171..0000000000 --- a/app/Helpers/Report/ReportQuery.php +++ /dev/null @@ -1,109 +0,0 @@ - '123.45' - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function earnedPerMonth(Collection $accounts, Carbon $start, Carbon $end): array - { - $ids = $accounts->pluck('id')->toArray(); - $query = Auth::user()->transactionjournals() - ->leftJoin( - 'transactions AS t_from', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_to', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); - } - ) - ->whereIn('t_to.account_id', $ids) - ->whereNotIn('t_from.account_id', $ids) - ->after($start) - ->before($end) - ->transactionTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]) - ->groupBy('dateFormatted') - ->get( - [ - DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'), - DB::raw('SUM(`t_to`.`amount`) AS `sum`'), - ] - ); - $array = []; - foreach ($query as $result) { - $array[$result->dateFormatted] = $result->sum; - } - - return $array; - } - - /** - * Returns an array of the amount of money spent in the given accounts (on withdrawals, opening balances and transfers) - * grouped by month like so: "2015-01" => '123.45' - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function spentPerMonth(Collection $accounts, Carbon $start, Carbon $end): array - { - $ids = $accounts->pluck('id')->toArray(); - $query = Auth::user()->transactionjournals() - ->leftJoin( - 'transactions AS t_from', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_from.transaction_journal_id')->where('t_from.amount', '<', 0); - } - ) - ->leftJoin( - 'transactions AS t_to', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 't_to.transaction_journal_id')->where('t_to.amount', '>', 0); - } - ) - ->whereIn('t_from.account_id', $ids) - ->whereNotIn('t_to.account_id', $ids) - ->after($start) - ->before($end) - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE]) - ->groupBy('dateFormatted') - ->get( - [ - DB::raw('DATE_FORMAT(`transaction_journals`.`date`,"%Y-%m") AS `dateFormatted`'), - DB::raw('SUM(`t_from`.`amount`) AS `sum`'), - ] - ); - $array = []; - foreach ($query as $result) { - $array[$result->dateFormatted] = $result->sum; - } - - return $array; - - } -} diff --git a/app/Helpers/Report/ReportQueryInterface.php b/app/Helpers/Report/ReportQueryInterface.php deleted file mode 100644 index 4dba7f3d5d..0000000000 --- a/app/Helpers/Report/ReportQueryInterface.php +++ /dev/null @@ -1,42 +0,0 @@ - '123.45' - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function earnedPerMonth(Collection $accounts, Carbon $start, Carbon $end): array; - - /** - * Returns an array of the amount of money spent in the given accounts (on withdrawals, opening balances and transfers) - * grouped by month like so: "2015-01" => '123.45' - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function spentPerMonth(Collection $accounts, Carbon $start, Carbon $end): array; - - -} From f78d56b149d0d1f972307ca564138195f07dc4dc Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 17:08:28 +0200 Subject: [PATCH 119/206] Will implement changes to test database. --- .gitignore | 1 - database/seeds/DatabaseSeeder.php | 7 +- database/seeds/SplitDataSeeder.php | 3 +- database/seeds/TestDataSeeder.php | 69 --- storage/database/seed.local.json | 788 +++++++++++++++++++++++++++++ storage/debugbar/.gitignore | 2 + 6 files changed, 793 insertions(+), 77 deletions(-) create mode 100644 storage/database/seed.local.json create mode 100644 storage/debugbar/.gitignore diff --git a/.gitignore b/.gitignore index 24a6fb22e5..8edc368963 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ /node_modules .env -storage/ .env.local diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 45131e047b..8975b71238 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -22,14 +22,9 @@ class DatabaseSeeder extends Seeder $this->call('TransactionTypeSeeder'); $this->call('PermissionSeeder'); - // set up basic test data (as little as possible): - if (App::environment() == 'testing' || App::environment() == 'local') { + if (App::environment() == 'testing') { $this->call('TestDataSeeder'); } - // set up basic test data (as little as possible): - if (App::environment() == 'split') { - $this->call('SplitDataSeeder'); - } } } diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php index 1134bc6097..8e734eb4ae 100644 --- a/database/seeds/SplitDataSeeder.php +++ b/database/seeds/SplitDataSeeder.php @@ -102,7 +102,7 @@ class SplitDataSeeder extends Seeder $categories = ['Salary', 'Reimbursements']; $amounts = [50, 50]; $destination = TestData::findAccount($user, 'Alternate Checking Account'); - $date = new Carbon('2012-03-15'); + $date = new Carbon('2012-03-12'); $journal = TransactionJournal::create( ['user_id' => $user->id, 'transaction_type_id' => 2, 'transaction_currency_id' => 1, 'description' => 'Split Even Income (journal (50/50))', 'completed' => 1, 'date' => $date->format('Y-m-d'),] @@ -134,6 +134,7 @@ class SplitDataSeeder extends Seeder $amounts = [15, 34, 51]; $destination = TestData::findAccount($user, 'Checking Account'); $date = new Carbon; + $date->subDays(3); $journal = TransactionJournal::create( ['user_id' => $user->id, 'transaction_type_id' => 2, 'transaction_currency_id' => 1, 'description' => 'Split Uneven Income (journal (15/34/51=100))', 'completed' => 1, 'date' => $date->format('Y-m-d'),] diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index b58b090c47..1fe98fffe5 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -41,81 +41,12 @@ class TestDataSeeder extends Seeder */ public function run() { - // start by creating all users: - // method will return the first user. - $user = TestData::createUsers(); - // create all kinds of static data: - TestData::createAssetAccounts($user, []); - TestData::createBills($user); - TestData::createBudgets($user); - TestData::createCategories($user); - TestData::createPiggybanks($user, 'TestData Savings'); - TestData::createExpenseAccounts($user); - TestData::createRevenueAccounts($user); - TestData::createAttachments($user, $this->start); - TestData::openingBalanceSavings($user, $this->start); - TestData::createRules($user); - - // loop from start to end, create dynamic info. $current = clone $this->start; while ($current < $this->end) { $month = $current->format('F Y'); - // create salaries: - TestData::createIncome($user, 'Salary ' . $month, $current, strval(rand(2000, 2100))); - - // pay bills: - TestData::createRent($user, 'Rent for ' . $month, $current, '800'); - TestData::createWater($user, 'Water bill for ' . $month, $current, '15'); - TestData::createTV($user, 'TV bill for ' . $month, $current, '60'); - TestData::createPower($user, 'Power bill for ' . $month, $current, '120'); - - // pay daily groceries: - TestData::createGroceries($user, $current); - - // create tag (each type of tag, for date): - TestData::createTags($user, $current); - - // go out for drinks: - TestData::createDrinksAndOthers($user, $current); - - // save money every month: - TestData::createSavings($user, $current); - - // buy gas for the car every month: - TestData::createCar($user, $current); - - // create budget limits. - TestData::createBudgetLimit($user, $current, 'Groceries', '400'); - TestData::createBudgetLimit($user, $current, 'Bills', '1000'); - TestData::createBudgetLimit($user, $current, 'Car', '100'); $current->addMonth(); } - - // create some special budget limits to test stuff with multiple budget limits - // for a range of dates: - $this->end->startOfMonth()->addDay(); - - $budget = TestData::findBudget($user, 'Bills'); - $ranges = ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly']; - foreach ($ranges as $range) { - $limit = BudgetLimit::create( - [ - 'budget_id' => $budget->id, - 'startdate' => $this->end->format('Y-m-d'), - 'amount' => rand(100, 200), - 'repeats' => 0, - 'repeat_freq' => $range, - ] - ); - // also trigger event. - $thisEnd = Navigation::addPeriod($this->end, $range, 0); - $thisEnd->subDay(); - event(new BudgetLimitStored($limit, $thisEnd)); - $this->end->addDay(); - } - - // b } } diff --git a/storage/database/seed.local.json b/storage/database/seed.local.json new file mode 100644 index 0000000000..2b6a8f17ce --- /dev/null +++ b/storage/database/seed.local.json @@ -0,0 +1,788 @@ +{ + "users": [ + { + "email": "thegrumpydictator@gmail.com", + "password": "james" + }, + { + "email": "thegrumpydictator+empty@gmail.com", + "password": "james" + }, + { + "email": "thegrumpydictator+deleteme@gmail.com", + "password": "james" + } + ], + "roles": [ + { + "user_id": 1, + "role": "owner" + } + ], + "accounts": [ + { + "user_id": 1, + "account_type_id": 3, + "name": "Checking Account", + "iban": "NL11XOLA6707795988" + }, + { + "user_id": 1, + "account_type_id": 3, + "name": "Alternate Checking Account", + "iban": "NL40UKBK3619908726" + }, + { + "user_id": 1, + "account_type_id": 3, + "name": "Savings Account", + "iban": "NL96DZCO4665940223" + }, + { + "user_id": 1, + "account_type_id": 3, + "name": "Shared Checking Account", + "iban": "NL81RCQZ7160379858" + }, + { + "user_id": 1, + "account_type_id": 3, + "name": "Emergency Savings Account", + "iban": "NL38SRMN4325934708" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Adobe" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Google" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Vitens" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Albert Heijn" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "PLUS", + "0": "Apple" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Bakker" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Belastingdienst" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "bol.com" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Cafe Central" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "conrad.nl" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Coolblue" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Shell" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "SixtyFive" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "EightyFour" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Fiftyone" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "DUO" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Etos" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "FEBO" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Greenchoice" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Halfords" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "XS4All" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "iCentre" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Jumper" + }, + { + "user_id": 1, + "account_type_id": 4, + "name": "Land lord" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Job" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Belastingdienst" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Bank" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "KPN" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Google" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Work SixtyFive" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Work EightyFour" + }, + { + "user_id": 1, + "account_type_id": 5, + "name": "Work Fiftyone" + }, + { + "user_id": 1, + "account_type_id": 6, + "name": "Opposing for Savings Account" + }, + { + "user_id": 1, + "account_type_id": 6, + "name": "Opposing for Emergency Savings Account" + } + ], + "account-meta": [ + { + "account_id": 1, + "name": "accountRole", + "data": "\"defaultAsset\"" + }, + { + "account_id": 2, + "name": "accountRole", + "data": "\"defaultAsset\"" + }, + { + "account_id": 3, + "name": "accountRole", + "data": "\"savingAsset\"" + }, + { + "account_id": 4, + "name": "accountRole", + "data": "\"sharedAsset\"" + } + ], + "bills": [ + { + "name": "Rent", + "match": "rent,land,lord", + "amount_min": 795, + "amount_max": 805, + "user_id": 1, + "date": "2015-01-01", + "active": 1, + "automatch": 1, + "repeat_freq": "monthly", + "skip": 0 + }, + { + "name": "Health insurance", + "match": "insurer,insurance,health", + "amount_min": 120, + "amount_max": 140, + "user_id": 1, + "date": "2015-01-01", + "active": 1, + "automatch": 1, + "repeat_freq": "monthly", + "skip": 0 + } + ], + "budgets": [ + { + "name": "Groceries", + "user_id": 1 + }, + { + "name": "Bills", + "user_id": 1 + }, + { + "name": "Car", + "user_id": 1 + }, + { + "name": "One Empty Budget", + "user_id": 1 + }, + { + "name": "Another Empty Budget", + "user_id": 1 + } + ], + "budget-limits": [ + { + "budget_id": 1, + "startdate": "2016-05-01", + "amount": 196, + "repeats": 0, + "repeat_freq": "daily" + }, + { + "budget_id": 1, + "startdate": "2016-05-01", + "amount": 154, + "repeats": 0, + "repeat_freq": "weekly" + }, + { + "budget_id": 1, + "startdate": "2016-05-01", + "amount": 159, + "repeats": 0, + "repeat_freq": "monthly" + }, + { + "budget_id": 1, + "startdate": "2016-05-01", + "amount": 157, + "repeats": 0, + "repeat_freq": "quarterly" + }, + { + "budget_id": 1, + "startdate": "2016-05-01", + "amount": 190, + "repeats": 0, + "repeat_freq": "half-year" + }, + { + "budget_id": 1, + "startdate": "2016-05-01", + "amount": 145, + "repeats": 0, + "repeat_freq": "yearly" + } + ], + "monthly-limits": [ + { + "budget_id": 1, + "amount": 100 + }, + { + "budget_id": 2, + "amount": 100 + }, + { + "budget_id": 3, + "amount": 100 + } + ], + "categories": [ + { + "name": "Groceries", + "user_id": 1 + }, + { + "name": "Car", + "user_id": 1 + }, + { + "name": "Reimbursements", + "user_id": 1 + }, + { + "name": "Salary", + "user_id": 1 + } + ], + "piggy-banks": [ + { + "account_id": 3, + "name": "New camera", + "targetamount": 1000, + "startdate": "2015-04-01", + "reminder_skip": 0, + "remind_me": 0, + "order": 1, + "currentamount": 735 + }, + { + "account_id": 3, + "name": "New phone", + "targetamount": 600, + "startdate": "2015-04-01", + "reminder_skip": 0, + "remind_me": 0, + "order": 2, + "currentamount": 333 + }, + { + "account_id": 3, + "name": "New couch", + "targetamount": 500, + "startdate": "2015-04-01", + "reminder_skip": 0, + "remind_me": 0, + "order": 3, + "currentamount": 120 + } + ], + "piggy-events": [ + { + "piggy_bank_id": 1, + "date": "2015-05-01", + "amount": 245 + }, + { + "piggy_bank_id": 1, + "date": "2015-06-02", + "amount": 245 + }, + { + "piggy_bank_id": 1, + "date": "2015-07-03", + "amount": 245 + }, + { + "piggy_bank_id": 2, + "date": "2015-08-04", + "amount": 111 + }, + { + "piggy_bank_id": 2, + "date": "2015-09-05", + "amount": 111 + }, + { + "piggy_bank_id": 2, + "date": "2015-10-06", + "amount": 111 + }, + { + "piggy_bank_id": 3, + "date": "2015-11-07", + "amount": 40 + }, + { + "piggy_bank_id": 3, + "date": "2015-12-08", + "amount": 40 + }, + { + "piggy_bank_id": 3, + "date": "2016-01-09", + "amount": 40 + } + ], + "rule-groups": [ + { + "user_id": 1, + "order": 1, + "title": "Default rules", + "description": "All your rules not in a particular group." + } + ], + "rules": [ + { + "user_id": 1, + "rule_group_id": 1, + "order": 1, + "active": 1, + "stop_processing": 0, + "title": "Your first default rule", + "description": "This rule is an example. You can safely delete it." + } + ], + "rule-triggers": [ + { + "rule_id": 1, + "order": 1, + "active": 1, + "stop_processing": 0, + "trigger_type": "user_action", + "trigger_value": "store-journal" + }, + { + "rule_id": 1, + "order": 2, + "active": 1, + "stop_processing": 0, + "trigger_type": "description_is", + "trigger_value": "The Man Who Sold the World" + }, + { + "rule_id": 1, + "order": 3, + "active": 1, + "stop_processing": 0, + "trigger_type": "from_account_is", + "trigger_value": "David Bowie" + } + ], + "rule-actions": [ + { + "rule_id": 1, + "order": 1, + "active": 1, + "action_type": "prepend_description", + "action_value": "Bought the world from " + }, + { + "rule_id": 1, + "order": 2, + "active": 1, + "action_type": "set_category", + "action_value": "Large expenses" + } + ], + "tags": [ + { + "user_id": 1, + "tag": "TagJanuary", + "tagMode": "nothing", + "date": "2015-01-01" + }, + { + "user_id": 1, + "tag": "TagFebruary", + "tagMode": "nothing", + "date": "2015-02-02" + }, + { + "user_id": 1, + "tag": "TagMarch", + "tagMode": "nothing", + "date": "2015-03-03" + }, + { + "user_id": 1, + "tag": "TagApril", + "tagMode": "nothing", + "date": "2015-04-04" + }, + { + "user_id": 1, + "tag": "TagMay", + "tagMode": "nothing", + "date": "2015-05-05" + }, + { + "user_id": 1, + "tag": "TagJune", + "tagMode": "nothing", + "date": "2015-06-06" + }, + { + "user_id": 1, + "tag": "TagJuly", + "tagMode": "nothing", + "date": "2015-07-07" + }, + { + "user_id": 1, + "tag": "TagAugust", + "tagMode": "nothing", + "date": "2015-08-08" + }, + { + "user_id": 1, + "tag": "TagSeptember", + "tagMode": "nothing", + "date": "2015-09-09" + }, + { + "user_id": 1, + "tag": "TagOctober", + "tagMode": "nothing", + "date": "2015-10-10" + }, + { + "user_id": 1, + "tag": "TagNovember", + "tagMode": "nothing", + "date": "2015-11-11" + }, + { + "user_id": 1, + "tag": "TagDecember", + "tagMode": "nothing", + "date": "2015-12-12" + } + ], + "monthly-deposits": [ + { + "user": 1, + "day-of-month": 24, + "description": "Salary in :month", + "source": "Job", + "destination": "Checking Account", + "min_amount": 2000, + "max_amount": 2100 + } + ], + "monthly-transfers": [ + { + "user": 1, + "day-of-month": 28, + "description": "Saving money for :month", + "source": "Checking Account", + "destination": "Savings Account", + "min_amount": 150, + "max_amount": 150 + } + ], + "monthly-withdrawals": [ + { + "user": 1, + "day-of-month": 2, + "description": "Rent for :month", + "source": "Checking Account", + "destination": "Land lord", + "min_amount": 800, + "max_amount": 800 + }, + { + "user": 1, + "day-of-month": 4, + "description": "Water bill :month", + "source": "Checking Account", + "destination": "Land lord", + "min_amount": 800, + "max_amount": 800 + }, + { + "user": 1, + "day-of-month": 6, + "description": "TV bill :month", + "source": "Checking Account", + "destination": "Land lord", + "min_amount": 800, + "max_amount": 800 + }, + { + "user": 1, + "day-of-month": 8, + "description": "Power bill :month", + "source": "Checking Account", + "destination": "Land lord", + "min_amount": 800, + "max_amount": 800 + }, + { + "user": 1, + "day-of-month": 7, + "description": "Bought gas", + "source": "Checking Account", + "destination": "Shell", + "min_amount": 40, + "max_amount": 50 + }, + { + "user": 1, + "day-of-month": 17, + "description": "Filled the car up again", + "source": "Checking Account", + "destination": "Shell", + "min_amount": 40, + "max_amount": 50 + }, + { + "user": 1, + "day-of-month": 27, + "description": "Needed gas again", + "source": "Checking Account", + "destination": "Shell", + "min_amount": 45, + "max_amount": 55 + }, + { + "user": 1, + "day-of-month": 2, + "description": "Groceries", + "source": "Checking Account", + "destination": "Albert Heijn", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 6, + "description": "Groceries", + "source": "Checking Account", + "destination": "PLUS", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 8, + "description": "Groceries", + "source": "Checking Account", + "destination": "Bakker", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 11, + "description": "Groceries", + "source": "Checking Account", + "destination": "Albert Heijn", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 15, + "description": "Groceries", + "source": "Checking Account", + "destination": "PLUS", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 19, + "description": "Groceries", + "source": "Checking Account", + "destination": "Albert Heijn", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 23, + "description": "Groceries", + "source": "Checking Account", + "destination": "Bakker", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 26, + "description": "Groceries", + "source": "Checking Account", + "destination": "Albert Heijn", + "min_amount": 15, + "max_amount": 25 + }, + { + "user": 1, + "day-of-month": 23, + "description": "Going out for drinks", + "source": "Checking Account", + "destination": "Cafe Central", + "min_amount": 15, + "max_amount": 36 + }, + { + "user": 1, + "day-of-month": 26, + "description": "Going out for drinks again", + "source": "Checking Account", + "destination": "Cafe Central", + "min_amount": 15, + "max_amount": 36 + } + ], + "attachments": [ + { + "attachable_id": 1, + "attachable_type": "FireflyIII\\Models\\TransactionJournal", + "user_id": 1, + "content": "This is attachment number one.", + "filename": "empty-file.txt", + "title": "Empty file", + "description": "This file is empty", + "notes": "Some notes", + "mime": "text\/plain", + "uploaded": 1 + }, + { + "attachable_id": 2, + "attachable_type": "FireflyIII\\Models\\TransactionJournal", + "user_id": 1, + "content": "This is attachment number two.", + "filename": "empty-file.txt", + "title": "Empty file", + "description": "This file is empty", + "notes": "Some notes", + "mime": "text\/plain", + "uploaded": 1 + } + ] +} \ No newline at end of file diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/storage/debugbar/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From e5eabdf7e70b3e35f4796b5d243374e7fe4dc257 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 21:49:16 +0200 Subject: [PATCH 120/206] Clean up test data. --- app/Support/Migration/TestData.php | 1507 ++++++++++------------------ config/filesystems.php | 8 +- database/seeds/DatabaseSeeder.php | 5 +- database/seeds/TestDataSeeder.php | 23 +- storage/database/seed.local.json | 282 +++--- 5 files changed, 730 insertions(+), 1095 deletions(-) diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index 5e6e0a08b5..ee367c81d5 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -2,6 +2,12 @@ declare(strict_types = 1); namespace FireflyIII\Support\Migration; +use Carbon\Carbon; +use Crypt; +use DB; +use Navigation; +use Storage; + /** * TestData.php * Copyright (C) 2016 thegrumpydictator@gmail.com @@ -10,30 +16,6 @@ namespace FireflyIII\Support\Migration; * of the MIT license. See the LICENSE file for details. */ -use Carbon\Carbon; -use Crypt; -use FireflyIII\Events\BudgetLimitStored; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountMeta; -use FireflyIII\Models\Attachment; -use FireflyIII\Models\Bill; -use FireflyIII\Models\Budget; -use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\Category; -use FireflyIII\Models\PiggyBank; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Role; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleAction; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\Tag; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\User; -use Navigation; -use Storage; - /** * Class TestData * @@ -41,994 +23,599 @@ use Storage; */ class TestData { - + /** @var array */ + private $data = []; + /** @var Carbon */ + private $end; + /** @var Carbon */ + private $start; /** - * @param User $user - * @param array $assets + * TestData constructor. * - * @return bool + * @param array $data */ - public static function createAssetAccounts(User $user, array $assets): bool + private function __construct(array $data) { - if (count($assets) == 0) { - $assets = [ - [ - 'name' => 'TestData Checking Account', - 'iban' => 'NL11XOLA6707795988', - 'meta' => [ - 'accountRole' => 'defaultAsset', - ], - ], - [ - 'name' => 'TestData Savings', - 'iban' => 'NL96DZCO4665940223', - 'meta' => [ - 'accountRole' => 'savingAsset', - ], - ], - [ - 'name' => 'TestData Shared', - 'iban' => 'NL81RCQZ7160379858', - 'meta' => [ - 'accountRole' => 'sharedAsset', - ], - ], - [ - 'name' => 'TestData Creditcard', - 'iban' => 'NL19NRAP2367994221', - 'meta' => [ - 'accountRole' => 'ccAsset', - 'ccMonthlyPaymentDate' => '2015-05-27', - 'ccType' => 'monthlyFull', - ], - ], - [ - 'name' => 'Emergencies', - 'iban' => 'NL40UKBK3619908726', - 'meta' => [ - 'accountRole' => 'savingAsset', - ], - ], - [ - 'name' => 'STE', - 'iban' => 'NL38SRMN4325934708', - 'meta' => [ - 'accountRole' => 'savingAsset', - ], - ], - ]; - } + $this->data = $data; + $start = new Carbon; + $start->startOfYear(); + $start->subYears(2); + $end = new Carbon; - foreach ($assets as $index => $entry) { - // create account: - $account = Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 3, - 'name' => $entry['name'], + $this->start = $start; + $this->end = $end; + } + + /** + * @param array $data + */ + public static function run(array $data) + { + $seeder = new TestData($data); + $seeder->go(); + } + + /** + * + */ + private function createAccounts() + { + if (isset($this->data['accounts']) && is_array($this->data['accounts'])) { + $insert = []; + foreach ($this->data['accounts'] as $account) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $account['user_id'], + 'account_type_id' => $account['account_type_id'], + 'name' => Crypt::encrypt($account['name']), 'active' => 1, 'encrypted' => 1, - 'iban' => $entry['iban'], - ] - ); - foreach ($entry['meta'] as $name => $value) { - AccountMeta::create(['account_id' => $account->id, 'name' => $name, 'data' => $value]); + 'virtual_balance' => 0, + 'iban' => $account['iban'] ?? null, + ]; + } + DB::table('accounts')->insert($insert); + } + if (isset($this->data['account-meta']) && is_array($this->data['account-meta'])) { + $insert = []; + foreach ($this->data['account-meta'] as $meta) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'account_id' => $meta['account_id'], + 'name' => $meta['name'], + 'data' => $meta['data'], + ]; + } + DB::table('account_meta')->insert($insert); + } + } + + /** + * + */ + private function createAttachments() + { + if (isset($this->data['attachments']) && is_array($this->data['attachments'])) { + $insert = []; + $disk = Storage::disk('upload'); + foreach ($this->data['attachments'] as $attachment) { + $data = Crypt::encrypt($attachment['content']); + $attachmentId = DB::table('attachments')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'attachable_id' => $attachment['attachable_id'], + 'attachable_type' => $attachment['attachable_type'], + 'user_id' => $attachment['user_id'], + 'md5' => md5($attachment['content']), + 'filename' => $attachment['filename'], + 'title' => $attachment['title'], + 'description' => $attachment['description'], + 'notes' => $attachment['notes'], + 'mime' => $attachment['mime'], + 'size' => strlen($attachment['content']), + 'uploaded' => 1, + ] + ); + + $disk->put('at-' . $attachmentId . '.data', $data); } } - - return true; - } /** - * @param User $user - * @param Carbon $start * - * @return TransactionJournal */ - public static function createAttachments(User $user, Carbon $start): TransactionJournal + private function createBills() { - - $args = [ - 'user' => $user, - 'description' => 'Some journal for attachment', - 'date' => $start, - 'from' => 'Job', - 'to' => 'TestData Checking Account', - 'amount' => '100', - 'transaction_type' => 2, - ]; - $journal = self::createJournal($args); - - // and now attachments - $encrypted = Crypt::encrypt('I are secret'); - $one = Attachment::create( - [ - 'attachable_id' => $journal->id, - 'attachable_type' => 'FireflyIII\Models\TransactionJournal', - 'user_id' => $user->id, - 'md5' => md5('Hallo'), - 'filename' => 'empty-file.txt', - 'title' => 'Empty file', - 'description' => 'This file is empty', - 'notes' => 'What notes', - 'mime' => 'text/plain', - 'size' => strlen($encrypted), - 'uploaded' => 1, - ] - ); - - - // and now attachment. - $two = Attachment::create( - [ - 'attachable_id' => $journal->id, - 'attachable_type' => 'FireflyIII\Models\TransactionJournal', - 'user_id' => $user->id, - 'md5' => md5('Ook hallo'), - 'filename' => 'empty-file-2.txt', - 'title' => 'Empty file 2', - 'description' => 'This file is empty too', - 'notes' => 'What notes do', - 'mime' => 'text/plain', - 'size' => strlen($encrypted), - 'uploaded' => 1, - ] - ); - // echo crypted data to the file. - $disk = Storage::disk('upload'); - $disk->put('at-' . $one->id . '.data', $encrypted); - $disk->put('at-' . $two->id . '.data', $encrypted); - - return $journal; + if (isset($this->data['bills']) && is_array($this->data['bills'])) { + $insert = []; + foreach ($this->data['bills'] as $bill) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $bill['user_id'], + 'name' => Crypt::encrypt($bill['name']), + 'match' => Crypt::encrypt($bill['match']), + 'amount_min' => $bill['amount_min'], + 'amount_max' => $bill['amount_max'], + 'date' => $bill['date'], + 'active' => $bill['active'], + 'automatch' => $bill['automatch'], + 'repeat_freq' => $bill['repeat_freq'], + 'skip' => $bill['skip'], + 'name_encrypted' => 1, + 'match_encrypted' => 1, + ]; + } + DB::table('bills')->insert($insert); + } } /** - * @param User $user * - * @return bool */ - public static function createBills(User $user): bool + private function createBudgets() { - Bill::create( - [ - 'name' => 'Rent', - 'match' => 'rent,land,lord', - 'amount_min' => 795, - 'amount_max' => 805, - 'user_id' => $user->id, - 'date' => '2015-01-01', - 'active' => 1, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, - ] - ); - Bill::create( - [ - 'name' => 'Health insurance', - 'match' => 'zilveren,kruis,health', - 'amount_min' => 120, - 'amount_max' => 140, - 'user_id' => $user->id, - 'date' => '2015-01-01', - 'active' => 1, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, - ] - ); - - return true; - - } - - /** - * @param User $user - * @param Carbon $current - * @param string $name - * @param string $amount - * - * @return BudgetLimit - */ - public static function createBudgetLimit(User $user, Carbon $current, string $name, string $amount): BudgetLimit - { - $start = clone $current; - $end = clone $current; - $budget = self::findBudget($user, $name); - $start->startOfMonth(); - $end->endOfMonth(); - - $limit = BudgetLimit::create( - [ - 'budget_id' => $budget->id, - 'startdate' => $start->format('Y-m-d'), - 'amount' => $amount, - 'repeats' => 0, - 'repeat_freq' => 'monthly', - ] - ); - // also trigger event. - $thisEnd = Navigation::addPeriod($start, 'monthly', 0); - $thisEnd->subDay(); - event(new BudgetLimitStored($limit, $thisEnd)); - - return $limit; - } - - /** - * @param User $user - * - * @return bool - */ - public static function createBudgets(User $user): bool - { - Budget::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]); - Budget::firstOrCreateEncrypted(['name' => 'Bills', 'user_id' => $user->id]); - Budget::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); - - // some empty budgets. - foreach (['A'] as $letter) { - Budget::firstOrCreateEncrypted(['name' => 'Empty budget ' . $letter, 'user_id' => $user->id]); + if (isset($this->data['budgets']) && is_array($this->data['budgets'])) { + $insert = []; + foreach ($this->data['budgets'] as $budget) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $budget['user_id'], + 'name' => Crypt::encrypt($budget['name']), + 'encrypted' => 1, + ]; + } + DB::table('budgets')->insert($insert); } - return true; - } - - /** - * @param User $user - * @param Carbon $date - * - * @return TransactionJournal - */ - public static function createCar(User $user, Carbon $date): TransactionJournal - { - // twice: - $amount = strval(rand(4000, 5000) / 100); - $args = [ - 'user' => $user, - 'description' => 'Bought gas', - 'date' => new Carbon($date->format('Y-m') . '-10'),// paid on 10th - 'from' => 'TestData Checking Account', - 'to' => 'Shell', - 'amount' => $amount, - 'category' => 'Car', - 'budget' => 'Car', - ]; - self::createJournal($args); - - // again! - $args['date'] = new Carbon($date->format('Y-m') . '-20'); // paid on 20th - $args['amount'] = strval(rand(4000, 5000) / 100); - $args['description'] = 'Gas for car'; - $journal = self::createJournal($args); - - return $journal; - } - - /** - * @param User $user - * - * @return bool - */ - public static function createCategories(User $user): bool - { - Category::firstOrCreateEncrypted(['name' => 'Groceries', 'user_id' => $user->id]); - Category::firstOrCreateEncrypted(['name' => 'Car', 'user_id' => $user->id]); - Category::firstOrCreateEncrypted(['name' => 'Reimbursements', 'user_id' => $user->id]); - Category::firstOrCreateEncrypted(['name' => 'Salary', 'user_id' => $user->id]); - - return true; - } - - /** - * @param User $user - * @param Carbon $date - * - * @return bool - * - */ - public static function createDrinksAndOthers(User $user, Carbon $date): bool - { - $start = clone $date; - $end = clone $date; - $today = new Carbon; - $start->startOfMonth(); - $end->endOfMonth(); - $current = clone $start; - while ($current < $end && $current < $today) { - - // weekly drink: - $thisDate = clone $current; - $thisDate->addDay(); - $amount = strval(rand(1500, 3600) / 100); - $args = [ - 'user' => $user, - 'description' => 'Going out for drinks', - 'date' => $thisDate, - 'from' => 'TestData Checking Account', - 'to' => 'Cafe Central', - 'amount' => $amount, - 'category' => 'Drinks', - 'budget' => 'Going out', - ]; - self::createJournal($args); - - $current->addWeek(); - } - - return true; - } - - /** - * @param User $user - * - * @return bool - */ - public static function createExpenseAccounts(User $user): bool - { - $expenses = ['Adobe', 'Google', 'Vitens', 'Albert Heijn', 'PLUS', 'Apple', 'Bakker', 'Belastingdienst', 'bol.com', 'Cafe Central', 'conrad.nl', - 'Coolblue', 'Shell', 'SixtyFive', 'EightyFour', 'Fiftyone', - 'DUO', 'Etos', 'FEBO', 'Greenchoice', 'Halfords', 'XS4All', 'iCentre', 'Jumper', 'Land lord']; - foreach ($expenses as $name) { - // create account: - Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 4, - 'name' => $name, - 'active' => 1, - 'encrypted' => 1, - ] - ); - } - - return true; - - } - - /** - * @param User $user - * @param Carbon $date - * - * @return bool - * - */ - public static function createGroceries(User $user, Carbon $date): bool - { - $start = clone $date; - $end = clone $date; - $today = new Carbon; - $start->startOfMonth(); - $end->endOfMonth(); - - $stores = ['Albert Heijn', 'PLUS', 'Bakker']; - $descriptions = ['Groceries', 'Bought some groceries', 'Got groceries']; - - $current = clone $start; - while ($current < $end && $current < $today) { - // daily groceries: - $amount = (string)round((rand(1500, 2500) / 100), 2); - - $args = [ - 'user' => $user, - 'description' => $descriptions[rand(0, count($descriptions) - 1)], - 'date' => $current, - 'from' => 'TestData Checking Account', - 'to' => $stores[rand(0, count($stores) - 1)], - 'amount' => $amount, - 'category' => 'Daily groceries', - 'budget' => 'Groceries', - ]; - self::createJournal($args); - $current->addDay(); - } - - return true; - } - - /** - * @param User $user - * @param string $description - * @param Carbon $date - * @param string $amount - * - * @return TransactionJournal - */ - public static function createIncome(User $user, string $description, Carbon $date, string $amount): TransactionJournal - { - $date = new Carbon($date->format('Y-m') . '-23'); // paid on 23rd. - $today = new Carbon; - if ($date >= $today) { - return new TransactionJournal; - } - - // create journal: - $args = [ - 'user' => $user, - 'description' => $description, - 'date' => $date, - 'from' => 'Job', - 'to' => 'TestData Checking Account', - 'amount' => $amount, - 'category' => 'Salary', - 'transaction_type' => 2, - ]; - $journal = self::createJournal($args); - - return $journal; - - } - - /** - * @param array $opt - * - * @return TransactionJournal - */ - public static function createJournal(array $opt): TransactionJournal - { - $type = $opt['transaction_type'] ?? 1; - - $journal = TransactionJournal::create( - [ - 'user_id' => $opt['user']->id, - 'transaction_type_id' => $type, - 'transaction_currency_id' => 1, - 'description' => $opt['description'], - 'completed' => 1, - 'date' => $opt['date'], - ] - ); - self::createTransactions($journal, self::findAccount($opt['user'], $opt['from']), self::findAccount($opt['user'], $opt['to']), $opt['amount']); - if (isset($opt['category'])) { - $journal->categories()->save(self::findCategory($opt['user'], $opt['category'])); - } - if (isset($opt['budget'])) { - $journal->budgets()->save(self::findBudget($opt['user'], $opt['budget'])); - } - - return $journal; - } - - /** - * @param User $user - * @param string $accountName - * - * @return bool - * - */ - public static function createPiggybanks(User $user, string $accountName): bool - { - - $account = self::findAccount($user, $accountName); - $camera = PiggyBank::create( - [ - 'account_id' => $account->id, - 'name' => 'New camera', - 'targetamount' => 1000, - 'startdate' => '2015-04-01', - 'reminder_skip' => 0, - 'remind_me' => 0, - 'order' => 1, - ] - ); - $repetition = $camera->piggyBankRepetitions()->first(); - $repetition->currentamount = 735; - $repetition->save(); - - // events: - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $camera->id, - 'date' => '2015-05-01', - 'amount' => '245', - ] - ); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $camera->id, - 'date' => '2015-06-01', - 'amount' => '245', - ] - ); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $camera->id, - 'date' => '2015-07-01', - 'amount' => '245', - ] - ); - - - $phone = PiggyBank::create( - [ - 'account_id' => $account->id, - 'name' => 'New phone', - 'targetamount' => 600, - 'startdate' => '2015-04-01', - 'reminder_skip' => 0, - 'remind_me' => 0, - 'order' => 2, - ] - ); - $repetition = $phone->piggyBankRepetitions()->first(); - $repetition->currentamount = 333; - $repetition->save(); - - // events: - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $phone->id, - 'date' => '2015-05-01', - 'amount' => '111', - ] - ); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $phone->id, - 'date' => '2015-06-01', - 'amount' => '111', - ] - ); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $phone->id, - 'date' => '2015-07-01', - 'amount' => '111', - ] - ); - - $couch = PiggyBank::create( - [ - 'account_id' => $account->id, - 'name' => 'New couch', - 'targetamount' => 500, - 'startdate' => '2015-04-01', - 'reminder_skip' => 0, - 'remind_me' => 0, - 'order' => 3, - ] - ); - $repetition = $couch->piggyBankRepetitions()->first(); - $repetition->currentamount = 120; - $repetition->save(); - - // events: - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $couch->id, - 'date' => '2015-05-01', - 'amount' => '40', - ] - ); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $couch->id, - 'date' => '2015-06-01', - 'amount' => '40', - ] - ); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $couch->id, - 'date' => '2015-07-01', - 'amount' => '40', - ] - ); - - // empty one. - PiggyBank::create( - [ - 'account_id' => $account->id, - 'name' => 'New head set', - 'targetamount' => 500, - 'startdate' => '2015-04-01', - 'reminder_skip' => 0, - 'remind_me' => 0, - 'order' => 4, - ] - ); - - return true; - } - - /** - * @param User $user - * @param string $description - * @param Carbon $date - * @param string $amount - * - * @return TransactionJournal - */ - public static function createPower(User $user, string $description, Carbon $date, string $amount): TransactionJournal - { - $args = [ - 'user' => $user, - 'description' => $description, - 'date' => new Carbon($date->format('Y-m') . '-06'),// paid on 10th - 'from' => 'TestData Checking Account', - 'to' => 'Greenchoice', - 'amount' => $amount, - 'category' => 'House', - 'budget' => 'Bills', - ]; - $journal = self::createJournal($args); - - return $journal; - - } - - /** - * @param User $user - * @param string $description - * @param Carbon $date - * @param string $amount - * - * @return TransactionJournal - */ - public static function createRent(User $user, string $description, Carbon $date, string $amount): TransactionJournal - { - $args = [ - 'user' => $user, - 'description' => $description, - 'date' => $date, - 'from' => 'TestData Checking Account', - 'to' => 'Land lord', - 'amount' => $amount, - 'category' => 'Rent', - 'budget' => 'Bills', - ]; - $journal = self::createJournal($args); - - return $journal; - - } - - /** - * @param User $user - * - * @return bool - */ - public static function createRevenueAccounts(User $user): bool - { - $revenues = ['Job', 'Belastingdienst', 'Bank', 'KPN', 'Google', 'Work SixtyFive', 'Work EightyFour','Work Fiftyone']; - foreach ($revenues as $name) { - // create account: - Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 5, - 'name' => $name, - 'active' => 1, - 'encrypted' => 1, - ] - ); - } - - return true; - } - - /** - * @param User $user - * - * @return RuleGroup - */ - public static function createRules(User $user): RuleGroup - { - $group = RuleGroup::create( - [ - 'user_id' => $user->id, - 'order' => 1, - 'title' => trans('firefly.default_rule_group_name'), - 'description' => trans('firefly.default_rule_group_description'), - 'active' => 1, - ] - ); - $rule = Rule::create( - [ - 'user_id' => $user->id, - 'rule_group_id' => $group->id, - 'order' => 1, - 'active' => 1, - 'stop_processing' => 0, - 'title' => trans('firefly.default_rule_name'), - 'description' => trans('firefly.default_rule_description'), - ] - ); - - // three triggers: - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'order' => 1, - 'active' => 1, - 'stop_processing' => 0, - 'trigger_type' => 'user_action', - 'trigger_value' => 'store-journal', - ] - ); - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'order' => 2, - 'active' => 1, - 'stop_processing' => 0, - 'trigger_type' => 'description_is', - 'trigger_value' => trans('firefly.default_rule_trigger_description'), - ] - ); - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'order' => 3, - 'active' => 1, - 'stop_processing' => 0, - 'trigger_type' => 'from_account_is', - 'trigger_value' => trans('firefly.default_rule_trigger_from_account'), - ] - ); - - // two actions: - RuleAction::create( - [ - 'rule_id' => $rule->id, - 'order' => 1, - 'active' => 1, - 'action_type' => 'prepend_description', - 'action_value' => trans('firefly.default_rule_action_prepend'), - ] - ); - RuleAction::create( - [ - 'rule_id' => $rule->id, - 'order' => 1, - 'active' => 1, - 'action_type' => 'set_category', - 'action_value' => trans('firefly.default_rule_action_set_category'), - ] - ); - - return $group; - } - - /** - * @param User $user - * @param Carbon $date - * - * @return TransactionJournal - */ - public static function createSavings(User $user, Carbon $date): TransactionJournal - { - $args = [ - 'user' => $user, - 'description' => 'Save money', - 'date' => new Carbon($date->format('Y-m') . '-24'),// paid on 24th. - 'from' => 'TestData Checking Account', - 'to' => 'TestData Savings', - 'amount' => '150', - 'category' => 'Money management', - 'transaction_type' => 3, - ]; - $journal = self::createJournal($args); - - return $journal; - - } - - /** - * @param User $user - * @param string $description - * @param Carbon $date - * @param string $amount - * - * @return TransactionJournal - */ - public static function createTV(User $user, string $description, Carbon $date, string $amount): TransactionJournal - { - $args = [ - 'user' => $user, - 'description' => $description, - 'date' => new Carbon($date->format('Y-m') . '-15'), - 'from' => 'TestData Checking Account', - 'to' => 'XS4All', - 'amount' => $amount, - 'category' => 'House', - 'budget' => 'Bills', - ]; - $journal = self::createJournal($args); - - return $journal; - - } - - /** - * @param User $user - * @param Carbon $date - * - * @return Tag - */ - public static function createTags(User $user, Carbon $date): Tag - { - $title = 'SomeTag' . $date->month . '.' . $date->year . '.nothing'; - - $tag = Tag::create( - [ - 'user_id' => $user->id, - 'tag' => $title, - 'tagMode' => 'nothing', - 'date' => $date->format('Y-m-d'), - - - ] - ); - - return $tag; - } - - /** - * @param TransactionJournal $journal - * @param Account $from - * @param Account $to - * @param string $amount - * - * @return bool - */ - public static function createTransactions(TransactionJournal $journal, Account $from, Account $to, string $amount): bool - { - Transaction::create( - [ - 'account_id' => $from->id, - 'transaction_journal_id' => $journal->id, - 'amount' => bcmul($amount, '-1'), - - ] - ); - Transaction::create( - [ - 'account_id' => $to->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $amount, - - ] - ); - - return true; - } - - /** - * @return User - */ - public static function createUsers(): User - { - $user = User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); - User::create(['email' => 'thegrumpydictator+empty@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); - User::create(['email' => 'thegrumpydictator+deleteme@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); - - - $admin = Role::where('name', 'owner')->first(); - $user->attachRole($admin); - - return $user; - } - - /** - * @param User $user - * @param string $description - * @param Carbon $date - * @param string $amount - * - * @return TransactionJournal - */ - public static function createWater(User $user, string $description, Carbon $date, string $amount): TransactionJournal - { - $args = [ - 'user' => $user, - 'description' => $description, - 'date' => new Carbon($date->format('Y-m') . '-10'), // paid on 10th - 'from' => 'TestData Checking Account', - 'to' => 'Vitens', - 'amount' => $amount, - 'category' => 'House', - 'budget' => 'Bills', - ]; - $journal = self::createJournal($args); - - return $journal; - - } - - /** - * @param User $user - * @param $name - * - * @return Account - */ - public static function findAccount(User $user, string $name): Account - { - /** @var Account $account */ - foreach ($user->accounts()->get() as $account) { - if ($account->name == $name) { - return $account; + if (isset($this->data['budget-limits']) && is_array($this->data['budget-limits'])) { + foreach ($this->data['budget-limits'] as $limit) { + $amount = rand($limit['amount_min'], $limit['amount_max']); + $limitId = DB::table('budget_limits')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'budget_id' => $limit['budget_id'], + 'startdate' => $limit['startdate'], + 'amount' => $amount, + 'repeats' => 0, + 'repeat_freq' => $limit['repeat_freq'], + ] + ); + + DB::table('limit_repetitions')->insert( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'budget_limit_id' => $limitId, + 'startdate' => $limit['startdate'], + 'enddate' => Navigation::endOfPeriod(new Carbon($limit['startdate']), $limit['repeat_freq'])->format('Y-m-d'), + 'amount' => $amount, + ] + ); } } + if (isset($this->data['monthly-limits']) && is_array($this->data['monthly-limits'])) { + $current = clone $this->start; + while ($current <= $this->end) { + foreach ($this->data['monthly-limits'] as $limit) { + $amount = rand($limit['amount_min'], $limit['amount_max']); + $limitId = DB::table('budget_limits')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'budget_id' => $limit['budget_id'], + 'startdate' => $current->format('Y-m-d'), + 'amount' => $amount, + 'repeats' => 0, + 'repeat_freq' => 'monthly', + ] + ); - return new Account; + DB::table('limit_repetitions')->insert( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'budget_limit_id' => $limitId, + 'startdate' => $current->format('Y-m-d'), + 'enddate' => Navigation::endOfPeriod($current, 'monthly')->format('Y-m-d'), + 'amount' => $amount, + ] + ); + } + + $current->addMonth(); + } + + } } /** - * @param User $user - * @param $name * - * @return Budget */ - public static function findBudget(User $user, string $name): Budget + private function createCategories() { - /** @var Budget $budget */ - foreach (Budget::get() as $budget) { - if ($budget->name == $name && $user->id == $budget->user_id) { - return $budget; + if (isset($this->data['categories']) && is_array($this->data['categories'])) { + $insert = []; + foreach ($this->data['categories'] as $category) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $category['user_id'], + 'name' => Crypt::encrypt($category['name']), + 'encrypted' => 1, + ]; } + DB::table('categories')->insert($insert); + } + } + + /** + * + */ + private function createJournals() + { + $current = clone $this->start; + $transactions = []; + while ($current <= $this->end) { + $date = $current->format('Y-m-'); + $month = $current->format('F'); + + // run all monthly withdrawals: + if (isset($this->data['monthly-withdrawals']) && is_array($this->data['monthly-withdrawals'])) { + foreach ($this->data['monthly-withdrawals'] as $withdrawal) { + $description = str_replace(':month', $month, $withdrawal['description']); + $journalId = DB::table('transaction_journals')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $withdrawal['user_id'], + 'transaction_type_id' => 1, + 'bill_id' => $withdrawal['bill_id'] ?? null, + 'transaction_currency_id' => 1, + 'description' => Crypt::encrypt($description), + 'completed' => 1, + 'date' => $date . $withdrawal['day-of-month'], + 'interest_date' => $withdrawal['interest_date'] ?? null, + 'book_date' => $withdrawal['book_date'] ?? null, + 'process_date' => $withdrawal['process_date'] ?? null, + 'encrypted' => 1, + 'order' => 0, + 'tag_count' => 0, + ] + ); + $amount = (rand($withdrawal['min_amount'] * 100, $withdrawal['max_amount'] * 100)) / 100; + $transactions[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'transaction_journal_id' => $journalId, + 'account_id' => $withdrawal['source_id'], + 'amount' => $amount * -1, + ]; + $transactions[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'transaction_journal_id' => $journalId, + 'account_id' => $withdrawal['destination_id'], + 'amount' => $amount, + ]; + + // link to budget if set. + if (isset($withdrawal['budget_id'])) { + DB::table('budget_transaction_journal')->insert( + [ + 'budget_id' => $withdrawal['budget_id'], + 'transaction_journal_id' => $journalId, + + ] + ); + } + // link to category if set. + if (isset($withdrawal['category_id'])) { + DB::table('category_transaction_journal')->insert( + [ + 'category_id' => $withdrawal['category_id'], + 'transaction_journal_id' => $journalId, + + ] + ); + } + } + } + + // run all monthly deposits: + if (isset($this->data['monthly-deposits']) && is_array($this->data['monthly-deposits'])) { + foreach ($this->data['monthly-deposits'] as $deposit) { + $description = str_replace(':month', $month, $deposit['description']); + $journalId = DB::table('transaction_journals')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $deposit['user_id'], + 'transaction_type_id' => 2, + 'bill_id' => $deposit['bill_id'] ?? null, + 'transaction_currency_id' => 1, + 'description' => Crypt::encrypt($description), + 'completed' => 1, + 'date' => $date . $deposit['day-of-month'], + 'interest_date' => $deposit['interest_date'] ?? null, + 'book_date' => $deposit['book_date'] ?? null, + 'process_date' => $deposit['process_date'] ?? null, + 'encrypted' => 1, + 'order' => 0, + 'tag_count' => 0, + ] + ); + $amount = (rand($deposit['min_amount'] * 100, $deposit['max_amount'] * 100)) / 100; + $transactions[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'transaction_journal_id' => $journalId, + 'account_id' => $deposit['source_id'], + 'amount' => $amount * -1, + ]; + $transactions[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'transaction_journal_id' => $journalId, + 'account_id' => $deposit['destination_id'], + 'amount' => $amount, + ]; + + // link to category if set. + if (isset($deposit['category_id'])) { + DB::table('category_transaction_journal')->insert( + [ + 'category_id' => $deposit['category_id'], + 'transaction_journal_id' => $journalId, + + ] + ); + } + } + } + + // run all monthly transfers: + if (isset($this->data['monthly-transfers']) && is_array($this->data['monthly-transfers'])) { + foreach ($this->data['monthly-transfers'] as $transfer) { + $description = str_replace(':month', $month, $transfer['description']); + $journalId = DB::table('transaction_journals')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $transfer['user_id'], + 'transaction_type_id' => 3, + 'bill_id' => $transfer['bill_id'] ?? null, + 'transaction_currency_id' => 1, + 'description' => Crypt::encrypt($description), + 'completed' => 1, + 'date' => $date . $transfer['day-of-month'], + 'interest_date' => $transfer['interest_date'] ?? null, + 'book_date' => $transfer['book_date'] ?? null, + 'process_date' => $transfer['process_date'] ?? null, + 'encrypted' => 1, + 'order' => 0, + 'tag_count' => 0, + ] + ); + $amount = (rand($transfer['min_amount'] * 100, $transfer['max_amount'] * 100)) / 100; + $transactions[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'transaction_journal_id' => $journalId, + 'account_id' => $transfer['source_id'], + 'amount' => $amount * -1, + ]; + $transactions[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'transaction_journal_id' => $journalId, + 'account_id' => $transfer['destination_id'], + 'amount' => $amount, + ]; + // link to category if set. + if (isset($transfer['category_id'])) { + DB::table('category_transaction_journal')->insert( + [ + 'category_id' => $transfer['category_id'], + 'transaction_journal_id' => $journalId, + + ] + ); + } + } + } + + $current->addMonth(); } - return Budget::firstOrCreateEncrypted(['name' => $name, 'user_id' => $user->id]); + DB::table('transactions')->insert($transactions); } /** - * @param User $user - * @param $name * - * @return Category */ - public static function findCategory(User $user, string $name): Category + private function createPiggyBanks() { - /** @var Category $category */ - foreach (Category::get() as $category) { - if ($category->name == $name && $user->id == $category->user_id) { - return $category; + if (isset($this->data['piggy-banks']) && is_array($this->data['piggy-banks'])) { + foreach ($this->data['piggy-banks'] as $piggyBank) { + $piggyId = DB::table('piggy_banks')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'account_id' => $piggyBank['account_id'], + 'name' => Crypt::encrypt($piggyBank['name']), + 'targetamount' => $piggyBank['targetamount'], + 'startdate' => $piggyBank['startdate'], + 'reminder_skip' => 0, + 'remind_me' => 0, + 'order' => $piggyBank['order'], + 'encrypted' => 1, + ] + ); + if (isset($piggyBank['currentamount'])) { + DB::table('piggy_bank_repetitions')->insert( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'piggy_bank_id' => $piggyId, + 'startdate' => $piggyBank['startdate'], + 'currentamount' => $piggyBank['currentamount'], + ] + ); + } } } - - return Category::firstOrCreateEncrypted(['name' => $name, 'user_id' => $user->id]); } /** - * @param User $user - * @param Carbon $date * - * @return TransactionJournal */ - public static function openingBalanceSavings(User $user, Carbon $date): TransactionJournal + private function createRules() { - // opposing account for opening balance: - $opposing = Account::create( - [ - 'user_id' => $user->id, - 'account_type_id' => 6, - 'name' => 'Opposing for savings', - 'active' => 1, - 'encrypted' => 1, - ] - ); - - // savings - $savings = TestData::findAccount($user, 'TestData Savings'); - - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 4, - 'transaction_currency_id' => 1, - 'description' => 'Opening balance for savings account', - 'completed' => 1, - 'date' => $date->format('Y-m-d'), - ] - ); - self::createTransactions($journal, $opposing, $savings, '10000'); - - return $journal; + if (isset($this->data['rule-groups']) && is_array($this->data['rule-groups'])) { + $insert = []; + foreach ($this->data['rule-groups'] as $group) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $group['user_id'], + 'order' => $group['order'], + 'title' => $group['title'], + 'description' => $group['description'], + 'active' => 1, + ]; + } + DB::table('rule_groups')->insert($insert); + } + if (isset($this->data['rules']) && is_array($this->data['rules'])) { + $insert = []; + foreach ($this->data['rules'] as $rule) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $rule['user_id'], + 'rule_group_id' => $rule['rule_group_id'], + 'order' => $rule['order'], + 'active' => $rule['active'], + 'stop_processing' => $rule['stop_processing'], + 'title' => $rule['title'], + 'description' => $rule['description'], + ]; + } + DB::table('rules')->insert($insert); + } + if (isset($this->data['rule-triggers']) && is_array($this->data['rule-triggers'])) { + $insert = []; + foreach ($this->data['rule-triggers'] as $trigger) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'rule_id' => $trigger['rule_id'], + 'order' => $trigger['order'], + 'active' => $trigger['active'], + 'stop_processing' => $trigger['stop_processing'], + 'trigger_type' => $trigger['trigger_type'], + 'trigger_value' => $trigger['trigger_value'], + ]; + } + DB::table('rule_triggers')->insert($insert); + } + if (isset($this->data['rule-actions']) && is_array($this->data['rule-actions'])) { + $insert = []; + foreach ($this->data['rule-actions'] as $action) { + $insert[] = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'rule_id' => $action['rule_id'], + 'order' => $action['order'], + 'active' => $action['active'], + 'stop_processing' => $action['stop_processing'], + 'action_type' => $action['action_type'], + 'action_value' => $action['action_value'], + ]; + } + DB::table('rule_actions')->insert($insert); + } } + /** + * + */ + private function createTags() + { + if (isset($this->data['tags']) && is_array($this->data['tags'])) { + $insert = []; + foreach ($this->data['tags'] as $tag) { + $insert[] + = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $tag['user_id'], + 'tag' => Crypt::encrypt($tag['tag']), + 'tagMode' => $tag['tagMode'], + 'date' => $tag['date'] ?? null, + ]; + + } + DB::table('tags')->insert($insert); + } + } + + /** + * + */ + private function createUsers() + { + if (isset($this->data['users']) && is_array($this->data['users'])) { + $insert = []; + foreach ($this->data['users'] as $user) { + $insert[] + = [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'email' => $user['email'], + 'password' => bcrypt($user['password']), + ]; + + } + DB::table('users')->insert($insert); + } + if (isset($this->data['roles']) && is_array($this->data['roles'])) { + $insert = []; + foreach ($this->data['roles'] as $role) { + $insert[] + = [ + 'user_id' => $role['user_id'], + 'role_id' => $role['role'], + ]; + } + DB::table('role_user')->insert($insert); + } + } + + /** + * + */ + private function go() + { + $this->createUsers(); + $this->createAccounts(); + $this->createBills(); + $this->createBudgets(); + $this->createCategories(); + $this->createPiggyBanks(); + $this->createRules(); + $this->createTags(); + $this->createJournals(); + $this->createAttachments(); + } } diff --git a/config/filesystems.php b/config/filesystems.php index 70e2a2c488..e41406cf51 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -48,14 +48,18 @@ return [ 'root' => storage_path('app'), ], - 'upload' => [ + 'upload' => [ 'driver' => 'local', 'root' => storage_path('upload'), ], - 'export' => [ + 'export' => [ 'driver' => 'local', 'root' => storage_path('export'), ], + 'database' => [ + 'driver' => 'local', + 'root' => storage_path('database'), + ], 'ftp' => [ 'driver' => 'ftp', diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 8975b71238..9892663a49 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -21,10 +21,7 @@ class DatabaseSeeder extends Seeder $this->call('TransactionCurrencySeeder'); $this->call('TransactionTypeSeeder'); $this->call('PermissionSeeder'); - - if (App::environment() == 'testing') { - $this->call('TestDataSeeder'); - } + $this->call('TestDataSeeder'); } } diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index 1fe98fffe5..c0f18618c6 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -9,8 +9,6 @@ declare(strict_types = 1); */ use Carbon\Carbon; -use FireflyIII\Events\BudgetLimitStored; -use FireflyIII\Models\BudgetLimit; use FireflyIII\Support\Migration\TestData; use Illuminate\Database\Seeder; @@ -19,19 +17,11 @@ use Illuminate\Database\Seeder; */ class TestDataSeeder extends Seeder { - /** @var Carbon */ - public $end; - /** @var Carbon */ - public $start; - /** * TestDataSeeder constructor. */ public function __construct() { - $this->start = Carbon::create()->subYears(2)->startOfYear(); - $this->end = Carbon::now(); - } /** @@ -41,12 +31,13 @@ class TestDataSeeder extends Seeder */ public function run() { - - $current = clone $this->start; - while ($current < $this->end) { - $month = $current->format('F Y'); - - $current->addMonth(); + $disk = Storage::disk('database'); + $env = App::environment(); + $fileName = 'seed.' . $env . '.json'; + if ($disk->exists($fileName)) { + $file = json_decode($disk->get($fileName), true); + // run the file: + TestData::run($file); } } } diff --git a/storage/database/seed.local.json b/storage/database/seed.local.json index 2b6a8f17ce..7fd523c7ed 100644 --- a/storage/database/seed.local.json +++ b/storage/database/seed.local.json @@ -16,7 +16,7 @@ "roles": [ { "user_id": 1, - "role": "owner" + "role": 1 } ], "accounts": [ @@ -73,8 +73,7 @@ { "user_id": 1, "account_type_id": 4, - "name": "PLUS", - "0": "Apple" + "name": "PLUS" }, { "user_id": 1, @@ -290,69 +289,81 @@ { "name": "Another Empty Budget", "user_id": 1 + }, + { + "name": "Going out", + "user_id": 1 } ], "budget-limits": [ { "budget_id": 1, - "startdate": "2016-05-01", - "amount": 196, - "repeats": 0, + "startdate": "2016-04-01", + "amount_min": 100, + "amount_max": 200, "repeat_freq": "daily" }, { "budget_id": 1, "startdate": "2016-05-01", - "amount": 154, - "repeats": 0, + "amount_min": 100, + "amount_max": 200, "repeat_freq": "weekly" }, { "budget_id": 1, - "startdate": "2016-05-01", - "amount": 159, - "repeats": 0, + "startdate": "2016-06-01", + "amount_min": 100, + "amount_max": 200, "repeat_freq": "monthly" }, { "budget_id": 1, - "startdate": "2016-05-01", - "amount": 157, - "repeats": 0, + "startdate": "2016-07-01", + "amount_min": 100, + "amount_max": 200, "repeat_freq": "quarterly" }, { "budget_id": 1, - "startdate": "2016-05-01", - "amount": 190, - "repeats": 0, + "startdate": "2016-08-01", + "amount_min": 100, + "amount_max": 200, "repeat_freq": "half-year" }, { "budget_id": 1, - "startdate": "2016-05-01", - "amount": 145, - "repeats": 0, + "startdate": "2016-09-01", + "amount_min": 100, + "amount_max": 200, "repeat_freq": "yearly" } ], "monthly-limits": [ { "budget_id": 1, - "amount": 100 + "amount_min": 200, + "amount_max": 200 }, { "budget_id": 2, - "amount": 100 + "amount_min": 1000, + "amount_max": 1000 }, { "budget_id": 3, - "amount": 100 + "amount_min": 200, + "amount_max": 200 + }, + { + "budget_id": 6, + "amount_min": 100, + "amount_max": 100 } ], "categories": [ { - "name": "Groceries", + "name": "Daily groceries", "user_id": 1 }, { @@ -366,6 +377,14 @@ { "name": "Salary", "user_id": 1 + }, + { + "name": "Bills", + "user_id": 1 + }, + { + "name": "Going out", + "user_id": 1 } ], "piggy-banks": [ @@ -497,6 +516,7 @@ "rule_id": 1, "order": 1, "active": 1, + "stop_processing": 0, "action_type": "prepend_description", "action_value": "Bought the world from " }, @@ -504,6 +524,7 @@ "rule_id": 1, "order": 2, "active": 1, + "stop_processing": 0, "action_type": "set_category", "action_value": "Large expenses" } @@ -584,179 +605,214 @@ ], "monthly-deposits": [ { - "user": 1, + "user_id": 1, "day-of-month": 24, "description": "Salary in :month", - "source": "Job", - "destination": "Checking Account", - "min_amount": 2000, - "max_amount": 2100 + "source_id": 30, + "destination_id": 1, + "min_amount": 1500, + "max_amount": 1700, + "category_id": 4 } ], "monthly-transfers": [ { - "user": 1, + "user_id": 1, "day-of-month": 28, "description": "Saving money for :month", - "source": "Checking Account", - "destination": "Savings Account", + "source_id": 1, + "destination_id": 3, "min_amount": 150, "max_amount": 150 } ], "monthly-withdrawals": [ { - "user": 1, - "day-of-month": 2, + "user_id": 1, + "day-of-month": "02", "description": "Rent for :month", - "source": "Checking Account", - "destination": "Land lord", + "source_id": 1, + "destination_id": 29, "min_amount": 800, - "max_amount": 800 + "max_amount": 800, + "category_id": 5, + "budget_id": 2 }, { - "user": 1, - "day-of-month": 4, + "user_id": 1, + "day-of-month": "04", "description": "Water bill :month", - "source": "Checking Account", - "destination": "Land lord", - "min_amount": 800, - "max_amount": 800 + "source_id": 1, + "destination_id": 8, + "min_amount": 8, + "max_amount": 12, + "category_id": 5, + "budget_id": 2 }, { - "user": 1, - "day-of-month": 6, + "user_id": 1, + "day-of-month": "06", "description": "TV bill :month", - "source": "Checking Account", - "destination": "Land lord", - "min_amount": 800, - "max_amount": 800 + "source_id": 1, + "destination_id": 26, + "min_amount": 50, + "max_amount": 60, + "category_id": 5, + "budget_id": 2 }, { - "user": 1, - "day-of-month": 8, + "user_id": 1, + "day-of-month": "08", "description": "Power bill :month", - "source": "Checking Account", - "destination": "Land lord", - "min_amount": 800, - "max_amount": 800 + "source_id": 1, + "destination_id": 24, + "min_amount": 75, + "max_amount": 90, + "category_id": 5, + "budget_id": 2 }, { - "user": 1, - "day-of-month": 7, + "user_id": 1, + "day-of-month": "07", "description": "Bought gas", - "source": "Checking Account", - "destination": "Shell", + "source_id": 1, + "destination_id": 17, "min_amount": 40, - "max_amount": 50 + "max_amount": 50, + "category_id": 2, + "budget_id": 3 }, { - "user": 1, + "user_id": 1, "day-of-month": 17, "description": "Filled the car up again", - "source": "Checking Account", - "destination": "Shell", + "source_id": 1, + "destination_id": 17, "min_amount": 40, - "max_amount": 50 + "max_amount": 50, + "category_id": 2, + "budget_id": 3 }, { - "user": 1, + "user_id": 1, "day-of-month": 27, "description": "Needed gas again", - "source": "Checking Account", - "destination": "Shell", + "source_id": 1, + "destination_id": 17, "min_amount": 45, - "max_amount": 55 + "max_amount": 55, + "category_id": 2, + "budget_id": 3 }, { - "user": 1, - "day-of-month": 2, + "user_id": 1, + "day-of-month": "02", "description": "Groceries", - "source": "Checking Account", - "destination": "Albert Heijn", + "source_id": 1, + "destination_id": 9, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, - "day-of-month": 6, + "user_id": 1, + "day-of-month": "06", "description": "Groceries", - "source": "Checking Account", - "destination": "PLUS", + "source_id": 1, + "destination_id": 10, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, - "day-of-month": 8, + "user_id": 1, + "day-of-month": "08", "description": "Groceries", - "source": "Checking Account", - "destination": "Bakker", + "source_id": 1, + "destination_id": 11, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, + "user_id": 1, "day-of-month": 11, "description": "Groceries", - "source": "Checking Account", - "destination": "Albert Heijn", + "source_id": 1, + "destination_id": 9, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, + "user_id": 1, "day-of-month": 15, "description": "Groceries", - "source": "Checking Account", - "destination": "PLUS", + "source_id": 1, + "destination_id": 10, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, + "user_id": 1, "day-of-month": 19, "description": "Groceries", - "source": "Checking Account", - "destination": "Albert Heijn", + "source_id": 1, + "destination_id": 11, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, + "user_id": 1, "day-of-month": 23, "description": "Groceries", - "source": "Checking Account", - "destination": "Bakker", + "source_id": 1, + "destination_id": 9, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, + "user_id": 1, "day-of-month": 26, "description": "Groceries", - "source": "Checking Account", - "destination": "Albert Heijn", + "source_id": 1, + "destination_id": 10, "min_amount": 15, - "max_amount": 25 + "max_amount": 25, + "category_id": 1, + "budget_id": 1 }, { - "user": 1, - "day-of-month": 23, + "user_id": 1, + "day-of-month": 13, "description": "Going out for drinks", - "source": "Checking Account", - "destination": "Cafe Central", + "source_id": 1, + "destination_id": 14, "min_amount": 15, - "max_amount": 36 + "max_amount": 36, + "category_id": 6, + "budget_id": 6 }, { - "user": 1, + "user_id": 1, "day-of-month": 26, "description": "Going out for drinks again", - "source": "Checking Account", - "destination": "Cafe Central", + "source_id": 1, + "destination_id": 14, "min_amount": 15, - "max_amount": 36 + "max_amount": 36, + "category_id": 6, + "budget_id": 6 } ], "attachments": [ @@ -777,7 +833,7 @@ "attachable_type": "FireflyIII\\Models\\TransactionJournal", "user_id": 1, "content": "This is attachment number two.", - "filename": "empty-file.txt", + "filename": "empty-file2.txt", "title": "Empty file", "description": "This file is empty", "notes": "Some notes", From a547a5f3f90d20e2658ce61ad95d986a5920aed6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 21:50:02 +0200 Subject: [PATCH 121/206] New Chart library. [skip ci] --- public/js/lib/Chart.bundle.min.js | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/public/js/lib/Chart.bundle.min.js b/public/js/lib/Chart.bundle.min.js index bd2f96b501..3b96e43d2c 100755 --- a/public/js/lib/Chart.bundle.min.js +++ b/public/js/lib/Chart.bundle.min.js @@ -1,21 +1,16 @@ -!function t(e,i,a){function s(n,r){if(!i[n]){if(!e[n]){var l="function"==typeof require&&require;if(!r&&l)return l(n,!0);if(o)return o(n,!0);var h=new Error("Cannot find module '"+n+"'");throw h.code="MODULE_NOT_FOUND",h}var c=i[n]={exports:{}};e[n][0].call(c.exports,function(t){var i=e[n][1][t];return s(i?i:t)},c,c.exports,t,e,i,a)}return i[n].exports}for(var o="function"==typeof require&&require,n=0;ne&&(e+=360),a=(r+l)/2,i=l==r?0:.5>=a?h/(l+r):h/(2-l-r),[e,100*i,100*a]}function s(t){var e,i,a,s=t[0],o=t[1],n=t[2],r=Math.min(s,o,n),l=Math.max(s,o,n),h=l-r;return i=0==l?0:h/l*1e3/10,l==r?e=0:s==l?e=(o-n)/h:o==l?e=2+(n-s)/h:n==l&&(e=4+(s-o)/h),e=Math.min(60*e,360),0>e&&(e+=360),a=l/255*1e3/10,[e,i,a]}function o(t){var e=t[0],i=t[1],s=t[2],o=a(t)[0],n=1/255*Math.min(e,Math.min(i,s)),s=1-1/255*Math.max(e,Math.max(i,s));return[o,100*n,100*s]}function n(t){var e,i,a,s,o=t[0]/255,n=t[1]/255,r=t[2]/255;return s=Math.min(1-o,1-n,1-r),e=(1-o-s)/(1-s)||0,i=(1-n-s)/(1-s)||0,a=(1-r-s)/(1-s)||0,[100*e,100*i,100*a,100*s]}function l(t){return X[JSON.stringify(t)]}function h(t){var e=t[0]/255,i=t[1]/255,a=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,a=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92;var s=.4124*e+.3576*i+.1805*a,o=.2126*e+.7152*i+.0722*a,n=.0193*e+.1192*i+.9505*a;return[100*s,100*o,100*n]}function c(t){var e,i,a,s=h(t),o=s[0],n=s[1],r=s[2];return o/=95.047,n/=100,r/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*n-16,i=500*(o-n),a=200*(n-r),[e,i,a]}function u(t){return z(c(t))}function d(t){var e,i,a,s,o,n=t[0]/360,r=t[1]/100,l=t[2]/100;if(0==r)return o=255*l,[o,o,o];i=.5>l?l*(1+r):l+r-l*r,e=2*l-i,s=[0,0,0];for(var h=0;3>h;h++)a=n+1/3*-(h-1),0>a&&a++,a>1&&a--,o=1>6*a?e+6*(i-e)*a:1>2*a?i:2>3*a?e+(i-e)*(2/3-a)*6:e,s[h]=255*o;return s}function f(t){var e,i,a=t[0],s=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,s*=1>=o?o:2-o,i=(o+s)/2,e=2*s/(o+s),[a,100*e,100*i])}function m(t){return o(d(t))}function p(t){return n(d(t))}function v(t){return l(d(t))}function x(t){var e=t[0]/60,i=t[1]/100,a=t[2]/100,s=Math.floor(e)%6,o=e-Math.floor(e),n=255*a*(1-i),r=255*a*(1-i*o),l=255*a*(1-i*(1-o)),a=255*a;switch(s){case 0:return[a,l,n];case 1:return[r,a,n];case 2:return[n,a,l];case 3:return[n,r,a];case 4:return[l,n,a];case 5:return[a,n,r]}}function y(t){var e,i,a=t[0],s=t[1]/100,o=t[2]/100;return i=(2-s)*o,e=s*o,e/=1>=i?i:2-i,e=e||0,i/=2,[a,100*e,100*i]}function k(t){return o(x(t))}function _(t){return n(x(t))}function D(t){return l(x(t))}function S(t){var e,i,a,s,o=t[0]/360,n=t[1]/100,l=t[2]/100,h=n+l;switch(h>1&&(n/=h,l/=h),e=Math.floor(6*o),i=1-l,a=6*o-e,0!=(1&e)&&(a=1-a),s=n+a*(i-n),e){default:case 6:case 0:r=i,g=s,b=n;break;case 1:r=s,g=i,b=n;break;case 2:r=n,g=i,b=s;break;case 3:r=n,g=s,b=i;break;case 4:r=s,g=n,b=i;break;case 5:r=i,g=n,b=s}return[255*r,255*g,255*b]}function w(t){return a(S(t))}function C(t){return s(S(t))}function M(t){return n(S(t))}function A(t){return l(S(t))}function I(t){var e,i,a,s=t[0]/100,o=t[1]/100,n=t[2]/100,r=t[3]/100;return e=1-Math.min(1,s*(1-r)+r),i=1-Math.min(1,o*(1-r)+r),a=1-Math.min(1,n*(1-r)+r),[255*e,255*i,255*a]}function T(t){return a(I(t))}function F(t){return s(I(t))}function P(t){return o(I(t))}function O(t){return l(I(t))}function V(t){var e,i,a,s=t[0]/100,o=t[1]/100,n=t[2]/100;return e=3.2406*s+-1.5372*o+n*-.4986,i=s*-.9689+1.8758*o+.0415*n,a=.0557*s+o*-.204+1.057*n,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,a=a>.0031308?1.055*Math.pow(a,1/2.4)-.055:a=12.92*a,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),a=Math.min(Math.max(0,a),1),[255*e,255*i,255*a]}function W(t){var e,i,a,s=t[0],o=t[1],n=t[2];return s/=95.047,o/=100,n/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*o-16,i=500*(s-o),a=200*(o-n),[e,i,a]}function L(t){return z(W(t))}function R(t){var e,i,a,s,o=t[0],n=t[1],r=t[2];return 8>=o?(i=100*o/903.3,s=7.787*(i/100)+16/116):(i=100*Math.pow((o+16)/116,3),s=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(n/500+s-16/116)/7.787:95.047*Math.pow(n/500+s,3),a=.008859>=a/108.883?a=108.883*(s-r/200-16/116)/7.787:108.883*Math.pow(s-r/200,3),[e,i,a]}function z(t){var e,i,a,s=t[0],o=t[1],n=t[2];return e=Math.atan2(n,o),i=360*e/2/Math.PI,0>i&&(i+=360),a=Math.sqrt(o*o+n*n),[s,a,i]}function B(t){return V(R(t))}function Y(t){var e,i,a,s=t[0],o=t[1],n=t[2];return a=n/360*2*Math.PI,e=o*Math.cos(a),i=o*Math.sin(a),[s,e,i]}function N(t){return R(Y(t))}function H(t){return B(Y(t))}function E(t){return J[t]}function U(t){return a(E(t))}function j(t){return s(E(t))}function q(t){return o(E(t))}function G(t){return n(E(t))}function Z(t){return c(E(t))}function Q(t){return h(E(t))}e.exports={rgb2hsl:a,rgb2hsv:s,rgb2hwb:o,rgb2cmyk:n,rgb2keyword:l,rgb2xyz:h,rgb2lab:c,rgb2lch:u,hsl2rgb:d,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:x,hsv2hsl:y,hsv2hwb:k,hsv2cmyk:_,hsv2keyword:D,hwb2rgb:S,hwb2hsl:w,hwb2hsv:C,hwb2cmyk:M,hwb2keyword:A,cmyk2rgb:I,cmyk2hsl:T,cmyk2hsv:F,cmyk2hwb:P,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:q,keyword2cmyk:G,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:V,xyz2lab:W,xyz2lch:L,lab2xyz:R,lab2rgb:B,lab2lch:z,lch2lab:Y,lch2xyz:N,lch2rgb:H};var J={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},X={};for(var $ in J)X[JSON.stringify(J[$])]=$},{}],2:[function(t,e,i){var a=t("./conversions"),s=function(){return new h};for(var o in a){s[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),a[t](e)}}(o);var n=/(\w+)2(\w+)/.exec(o),r=n[1],l=n[2];s[r]=s[r]||{},s[r][l]=s[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=a[t](e);if("string"==typeof i||void 0===i)return i;for(var s=0;se||t[3]&&t[3]<1?u(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function u(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function d(t,e){if(1>e||t[3]&&t[3]<1)return f(t,e);var i=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgb("+i+"%, "+a+"%, "+s+"%)"}function f(t,e){var i=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgba("+i+"%, "+a+"%, "+s+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?m(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function b(t){return k[t.slice(0,3)]}function v(t,e,i){return Math.min(Math.max(e,t),i)}function x(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y=t("color-name");e.exports={getRgba:a,getHsla:s,getRgb:n,getHsl:r,getHwb:o,getAlpha:l,hexString:h,rgbString:c,rgbaString:u,percentString:d,percentaString:f,hslString:g,hslaString:m,hwbString:p,keyword:b};var k={};for(var _ in y)k[y[_]]=_},{"color-name":4}],4:[function(t,e,i){e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}],5:[function(t,e,i){var a=t("color-convert"),s=t("color-string"),o=function(t){if(t instanceof o)return t;if(!(this instanceof o))return new o(t);if(this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},"string"==typeof t){var e=s.getRgba(t);if(e)this.setValues("rgb",e);else if(e=s.getHsla(t))this.setValues("hsl",e);else{if(!(e=s.getHwb(t)))throw new Error('Unable to parse color from string "'+t+'"');this.setValues("hwb",e)}}else if("object"==typeof t){var e=t;if(void 0!==e.r||void 0!==e.red)this.setValues("rgb",e);else if(void 0!==e.l||void 0!==e.lightness)this.setValues("hsl",e);else if(void 0!==e.v||void 0!==e.value)this.setValues("hsv",e);else if(void 0!==e.w||void 0!==e.whiteness)this.setValues("hwb",e);else{if(void 0===e.c&&void 0===e.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(t));this.setValues("cmyk",e)}}};o.prototype={rgb:function(t){return this.setSpace("rgb",arguments)},hsl:function(t){return this.setSpace("hsl",arguments)},hsv:function(t){return this.setSpace("hsv",arguments)},hwb:function(t){return this.setSpace("hwb",arguments)},cmyk:function(t){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){return 1!==this.values.alpha?this.values.hwb.concat([this.values.alpha]):this.values.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values.rgb;return t.concat([this.values.alpha])},hslaArray:function(){var t=this.values.hsl;return t.concat([this.values.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return s.hexString(this.values.rgb)},rgbString:function(){return s.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return s.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return s.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return s.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return s.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return s.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return s.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){return this.values.rgb[0]<<16|this.values.rgb[1]<<8|this.values.rgb[2]},luminosity:function(){for(var t=this.values.rgb,e=[],i=0;i=a?a/12.92:Math.pow((a+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),i=t.luminosity();return e>i?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){return this.values.hsl[2]+=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},darken:function(t){return this.values.hsl[2]-=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},saturate:function(t){return this.values.hsl[1]+=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},desaturate:function(t){return this.values.hsl[1]-=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},whiten:function(t){return this.values.hwb[1]+=this.values.hwb[1]*t,this.setValues("hwb",this.values.hwb),this},blacken:function(t){return this.values.hwb[2]+=this.values.hwb[2]*t,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){return this.setValues("alpha",this.values.alpha-this.values.alpha*t),this},opaquer:function(t){return this.setValues("alpha",this.values.alpha+this.values.alpha*t),this},rotate:function(t){var e=this.values.hsl[0];return e=(e+t)%360,e=0>e?360+e:e,this.values.hsl[0]=e,this.setValues("hsl",this.values.hsl),this},mix:function(t,e){e=1-(null==e?.5:e);for(var i=2*e-1,a=this.alpha()-t.alpha(),s=((i*a==-1?i:(i+a)/(1+i*a))+1)/2,o=1-s,n=this.rgbArray(),r=t.rgbArray(),l=0;l0)for(i in Ri)a=Ri[i],s=e[a],"undefined"!=typeof s&&(t[a]=s);return t}function m(t){g(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),zi===!1&&(zi=!0,i.updateOffset(this),zi=!1)}function p(t){return t instanceof m||null!=t&&null!=t._isAMomentObject}function b(t){return 0>t?Math.ceil(t):Math.floor(t)}function v(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=b(e)),i}function x(t,e,i){var a,s=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),n=0;for(a=0;s>a;a++)(i&&t[a]!==e[a]||!i&&v(t[a])!==v(e[a]))&&n++;return n+o}function y(){}function k(t){return t?t.toLowerCase().replace("_","-"):t}function _(t){for(var e,i,a,s,o=0;o0;){if(a=D(s.slice(0,e).join("-")))return a;if(i&&i.length>=e&&x(s,i,!0)>=e-1)break;e--}o++}return null}function D(i){var a=null;if(!Bi[i]&&"undefined"!=typeof e&&e&&e.exports)try{a=Li._abbr,t("./locale/"+i),S(a)}catch(s){}return Bi[i]}function S(t,e){var i;return t&&(i="undefined"==typeof e?C(t):w(t,e),i&&(Li=i)),Li._abbr}function w(t,e){return null!==e?(e.abbr=t,Bi[t]=Bi[t]||new y,Bi[t].set(e),S(t),Bi[t]):(delete Bi[t],null)}function C(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Li;if(!s(t)){if(e=D(t))return e;t=[t]}return _(t)}function M(t,e){var i=t.toLowerCase();Yi[i]=Yi[i+"s"]=Yi[e]=t}function A(t){return"string"==typeof t?Yi[t]||Yi[t.toLowerCase()]:void 0}function I(t){var e,i,a={};for(i in t)r(t,i)&&(e=A(i),e&&(a[e]=t[i]));return a}function T(t,e){return function(a){return null!=a?(P(this,t,a),i.updateOffset(this,e),this):F(this,t)}}function F(t,e){return t._d["get"+(t._isUTC?"UTC":"")+e]()}function P(t,e,i){return t._d["set"+(t._isUTC?"UTC":"")+e](i)}function O(t,e){var i;if("object"==typeof t)for(i in t)this.set(i,t[i]);else if(t=A(t),"function"==typeof this[t])return this[t](e);return this}function V(t,e,i){var a=""+Math.abs(t),s=e-a.length,o=t>=0;return(o?i?"+":"":"-")+Math.pow(10,Math.max(0,s)).toString().substr(1)+a}function W(t,e,i,a){var s=a;"string"==typeof a&&(s=function(){return this[a]()}),t&&(Ui[t]=s),e&&(Ui[e[0]]=function(){return V(s.apply(this,arguments),e[1],e[2])}),i&&(Ui[i]=function(){return this.localeData().ordinal(s.apply(this,arguments),t)})}function L(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function R(t){var e,i,a=t.match(Ni);for(e=0,i=a.length;i>e;e++)Ui[a[e]]?a[e]=Ui[a[e]]:a[e]=L(a[e]);return function(s){var o="";for(e=0;i>e;e++)o+=a[e]instanceof Function?a[e].call(s,t):a[e];return o}}function z(t,e){return t.isValid()?(e=B(e,t.localeData()),Ei[e]=Ei[e]||R(e),Ei[e](t)):t.localeData().invalidDate()}function B(t,e){function i(t){return e.longDateFormat(t)||t}var a=5;for(Hi.lastIndex=0;a>=0&&Hi.test(t);)t=t.replace(Hi,i),Hi.lastIndex=0,a-=1;return t}function Y(t){return"function"==typeof t&&"[object Function]"===Object.prototype.toString.call(t)}function N(t,e,i){oa[t]=Y(e)?e:function(t){return t&&i?i:e}}function H(t,e){return r(oa,t)?oa[t](e._strict,e._locale):new RegExp(E(t))}function E(t){return t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,a,s){return e||i||a||s}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function U(t,e){var i,a=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(a=function(t,i){i[e]=v(t)}),i=0;ia;a++){if(s=h([2e3,a]),i&&!this._longMonthsParse[a]&&(this._longMonthsParse[a]=new RegExp("^"+this.months(s,"").replace(".","")+"$","i"),this._shortMonthsParse[a]=new RegExp("^"+this.monthsShort(s,"").replace(".","")+"$","i")),i||this._monthsParse[a]||(o="^"+this.months(s,"")+"|^"+this.monthsShort(s,""),this._monthsParse[a]=new RegExp(o.replace(".",""),"i")),i&&"MMMM"===e&&this._longMonthsParse[a].test(t))return a;if(i&&"MMM"===e&&this._shortMonthsParse[a].test(t))return a;if(!i&&this._monthsParse[a].test(t))return a}}function X(t,e){var i;return"string"==typeof e&&(e=t.localeData().monthsParse(e),"number"!=typeof e)?t:(i=Math.min(t.date(),G(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,i),t)}function $(t){return null!=t?(X(this,t),i.updateOffset(this,!0),this):F(this,"Month")}function K(){return G(this.year(),this.month())}function tt(t){var e,i=t._a;return i&&-2===u(t).overflow&&(e=i[la]<0||i[la]>11?la:i[ha]<1||i[ha]>G(i[ra],i[la])?ha:i[ca]<0||i[ca]>24||24===i[ca]&&(0!==i[ua]||0!==i[da]||0!==i[fa])?ca:i[ua]<0||i[ua]>59?ua:i[da]<0||i[da]>59?da:i[fa]<0||i[fa]>999?fa:-1,u(t)._overflowDayOfYear&&(ra>e||e>ha)&&(e=ha),u(t).overflow=e),t}function et(t){i.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function it(t,e){var i=!0;return l(function(){return i&&(et(t+"\n"+(new Error).stack),i=!1),e.apply(this,arguments)},e)}function at(t,e){pa[t]||(et(e),pa[t]=!0)}function st(t){var e,i,a=t._i,s=ba.exec(a);if(s){for(u(t).iso=!0,e=0,i=va.length;i>e;e++)if(va[e][1].exec(a)){t._f=va[e][0];break}for(e=0,i=xa.length;i>e;e++)if(xa[e][1].exec(a)){t._f+=(s[6]||" ")+xa[e][0];break}a.match(ia)&&(t._f+="Z"),Dt(t)}else t._isValid=!1}function ot(t){var e=ya.exec(t._i);return null!==e?void(t._d=new Date(+e[1])):(st(t),void(t._isValid===!1&&(delete t._isValid,i.createFromInputFallback(t))))}function nt(t,e,i,a,s,o,n){var r=new Date(t,e,i,a,s,o,n);return 1970>t&&r.setFullYear(t),r}function rt(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function lt(t){return ht(t)?366:365}function ht(t){return t%4===0&&t%100!==0||t%400===0}function ct(){return ht(this.year())}function ut(t,e,i){var a,s=i-e,o=i-t.day();return o>s&&(o-=7),s-7>o&&(o+=7),a=Ft(t).add(o,"d"),{week:Math.ceil(a.dayOfYear()/7),year:a.year()}}function dt(t){return ut(t,this._week.dow,this._week.doy).week}function ft(){return this._week.dow}function gt(){return this._week.doy}function mt(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function pt(t){var e=ut(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function bt(t,e,i,a,s){var o,n=6+s-a,r=rt(t,0,1+n),l=r.getUTCDay();return s>l&&(l+=7),i=null!=i?1*i:s,o=1+n+7*(e-1)-l+i,{year:o>0?t:t-1,dayOfYear:o>0?o:lt(t-1)+o}}function vt(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function xt(t,e,i){return null!=t?t:null!=e?e:i}function yt(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function kt(t){var e,i,a,s,o=[];if(!t._d){for(a=yt(t),t._w&&null==t._a[ha]&&null==t._a[la]&&_t(t),t._dayOfYear&&(s=xt(t._a[ra],a[ra]),t._dayOfYear>lt(s)&&(u(t)._overflowDayOfYear=!0),i=rt(s,0,t._dayOfYear),t._a[la]=i.getUTCMonth(),t._a[ha]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=a[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[ca]&&0===t._a[ua]&&0===t._a[da]&&0===t._a[fa]&&(t._nextDay=!0,t._a[ca]=0),t._d=(t._useUTC?rt:nt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[ca]=24)}}function _t(t){var e,i,a,s,o,n,r;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,n=4,i=xt(e.GG,t._a[ra],ut(Ft(),1,4).year),a=xt(e.W,1),s=xt(e.E,1)):(o=t._locale._week.dow,n=t._locale._week.doy,i=xt(e.gg,t._a[ra],ut(Ft(),o,n).year),a=xt(e.w,1),null!=e.d?(s=e.d,o>s&&++a):s=null!=e.e?e.e+o:o),r=bt(i,a,s,n,o),t._a[ra]=r.year,t._dayOfYear=r.dayOfYear}function Dt(t){if(t._f===i.ISO_8601)return void st(t);t._a=[],u(t).empty=!0;var e,a,s,o,n,r=""+t._i,l=r.length,h=0;for(s=B(t._f,t._locale).match(Ni)||[],e=0;e0&&u(t).unusedInput.push(n),r=r.slice(r.indexOf(a)+a.length),h+=a.length),Ui[o]?(a?u(t).empty=!1:u(t).unusedTokens.push(o),q(o,a,t)):t._strict&&!a&&u(t).unusedTokens.push(o);u(t).charsLeftOver=l-h,r.length>0&&u(t).unusedInput.push(r),u(t).bigHour===!0&&t._a[ca]<=12&&t._a[ca]>0&&(u(t).bigHour=void 0),t._a[ca]=St(t._locale,t._a[ca],t._meridiem),kt(t),tt(t)}function St(t,e,i){var a;return null==i?e:null!=t.meridiemHour?t.meridiemHour(e,i):null!=t.isPM?(a=t.isPM(i),a&&12>e&&(e+=12),a||12!==e||(e=0),e):e}function wt(t){var e,i,a,s,o;if(0===t._f.length)return u(t).invalidFormat=!0,void(t._d=new Date(NaN));for(s=0;so)&&(a=o,i=e));l(t,i||e)}function Ct(t){if(!t._d){var e=I(t._i);t._a=[e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],kt(t)}}function Mt(t){var e=new m(tt(At(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function At(t){var e=t._i,i=t._f;return t._locale=t._locale||C(t._l),null===e||void 0===i&&""===e?f({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),p(e)?new m(tt(e)):(s(i)?wt(t):i?Dt(t):o(e)?t._d=e:It(t),t))}function It(t){var e=t._i;void 0===e?t._d=new Date:o(e)?t._d=new Date(+e):"string"==typeof e?ot(t):s(e)?(t._a=n(e.slice(0),function(t){return parseInt(t,10)}),kt(t)):"object"==typeof e?Ct(t):"number"==typeof e?t._d=new Date(e):i.createFromInputFallback(t)}function Tt(t,e,i,a,s){var o={};return"boolean"==typeof i&&(a=i,i=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=s,o._l=i,o._i=t,o._f=e,o._strict=a,Mt(o)}function Ft(t,e,i,a){return Tt(t,e,i,a,!1)}function Pt(t,e){var i,a;if(1===e.length&&s(e[0])&&(e=e[0]),!e.length)return Ft();for(i=e[0],a=1;at&&(t=-t,i="-"),i+V(~~(t/60),2)+e+V(~~t%60,2)})}function zt(t){var e=(t||"").match(ia)||[],i=e[e.length-1]||[],a=(i+"").match(wa)||["-",0,0],s=+(60*a[1])+v(a[2]);return"+"===a[0]?s:-s}function Bt(t,e){var a,s;return e._isUTC?(a=e.clone(),s=(p(t)||o(t)?+t:+Ft(t))-+a,a._d.setTime(+a._d+s),i.updateOffset(a,!1),a):Ft(t).local()}function Yt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Nt(t,e){var a,s=this._offset||0;return null!=t?("string"==typeof t&&(t=zt(t)),Math.abs(t)<16&&(t=60*t),!this._isUTC&&e&&(a=Yt(this)),this._offset=t,this._isUTC=!0,null!=a&&this.add(a,"m"),s!==t&&(!e||this._changeInProgress?ae(this,$t(t-s,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,i.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?s:Yt(this)}function Ht(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Et(t){return this.utcOffset(0,t)}function Ut(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Yt(this),"m")),this}function jt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(zt(this._i)),this}function qt(t){return t=t?Ft(t).utcOffset():0,(this.utcOffset()-t)%60===0}function Gt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Zt(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var t={};if(g(t,this),t=At(t),t._a){var e=t._isUTC?h(t._a):Ft(t._a);this._isDSTShifted=this.isValid()&&x(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Qt(){return!this._isUTC}function Jt(){return this._isUTC}function Xt(){return this._isUTC&&0===this._offset}function $t(t,e){var i,a,s,o=t,n=null;return Lt(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(n=Ca.exec(t))?(i="-"===n[1]?-1:1,o={y:0,d:v(n[ha])*i,h:v(n[ca])*i,m:v(n[ua])*i,s:v(n[da])*i,ms:v(n[fa])*i}):(n=Ma.exec(t))?(i="-"===n[1]?-1:1,o={y:Kt(n[2],i),M:Kt(n[3],i),d:Kt(n[4],i),h:Kt(n[5],i),m:Kt(n[6],i),s:Kt(n[7],i),w:Kt(n[8],i)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(s=ee(Ft(o.from),Ft(o.to)),o={},o.ms=s.milliseconds,o.M=s.months),a=new Wt(o),Lt(t)&&r(t,"_locale")&&(a._locale=t._locale),a}function Kt(t,e){var i=t&&parseFloat(t.replace(",","."));return(isNaN(i)?0:i)*e}function te(t,e){var i={milliseconds:0,months:0};return i.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(i.months,"M").isAfter(e)&&--i.months,i.milliseconds=+e-+t.clone().add(i.months,"M"),i}function ee(t,e){var i;return e=Bt(e,t),t.isBefore(e)?i=te(t,e):(i=te(e,t),i.milliseconds=-i.milliseconds,i.months=-i.months),i}function ie(t,e){return function(i,a){var s,o;return null===a||isNaN(+a)||(at(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=i,i=a,a=o),i="string"==typeof i?+i:i,s=$t(i,a),ae(this,s,t),this}}function ae(t,e,a,s){var o=e._milliseconds,n=e._days,r=e._months;s=null==s?!0:s,o&&t._d.setTime(+t._d+o*a),n&&P(t,"Date",F(t,"Date")+n*a),r&&X(t,F(t,"Month")+r*a),s&&i.updateOffset(t,n||r)}function se(t,e){var i=t||Ft(),a=Bt(i,this).startOf("day"),s=this.diff(a,"days",!0),o=-6>s?"sameElse":-1>s?"lastWeek":0>s?"lastDay":1>s?"sameDay":2>s?"nextDay":7>s?"nextWeek":"sameElse";return this.format(e&&e[o]||this.localeData().calendar(o,this,Ft(i)))}function oe(){return new m(this)}function ne(t,e){var i;return e=A("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=p(t)?t:Ft(t),+this>+t):(i=p(t)?+t:+Ft(t),i<+this.clone().startOf(e))}function re(t,e){var i;return e=A("undefined"!=typeof e?e:"millisecond"),"millisecond"===e?(t=p(t)?t:Ft(t),+t>+this):(i=p(t)?+t:+Ft(t),+this.clone().endOf(e)e-o?(i=t.clone().add(s-1,"months"),a=(e-o)/(o-i)):(i=t.clone().add(s+1,"months"),a=(e-o)/(i-o)),-(s+a)}function de(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function fe(){var t=this.clone().utc();return 0e;e++)if(this._weekdaysParse[e]||(i=Ft([2e3,1]).day(e),a="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[e]=new RegExp(a.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e}function Ee(t){var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=ze(t,this.localeData()),this.add(t-e,"d")):e}function Ue(t){var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function je(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)}function qe(t,e){W(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function Ge(t,e){return e._meridiemParse}function Ze(t){return"p"===(t+"").toLowerCase().charAt(0)}function Qe(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"}function Je(t,e){e[fa]=v(1e3*("0."+t))}function Xe(){return this._isUTC?"UTC":""}function $e(){return this._isUTC?"Coordinated Universal Time":""}function Ke(t){return Ft(1e3*t)}function ti(){return Ft.apply(null,arguments).parseZone()}function ei(t,e,i){var a=this._calendar[t];return"function"==typeof a?a.call(e,i):a}function ii(t){var e=this._longDateFormat[t],i=this._longDateFormat[t.toUpperCase()];return e||!i?e:(this._longDateFormat[t]=i.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function ai(){return this._invalidDate}function si(t){return this._ordinal.replace("%d",t)}function oi(t){return t}function ni(t,e,i,a){var s=this._relativeTime[i];return"function"==typeof s?s(t,e,i,a):s.replace(/%d/i,t)}function ri(t,e){var i=this._relativeTime[t>0?"future":"past"];return"function"==typeof i?i(e):i.replace(/%s/i,e)}function li(t){var e,i;for(i in t)e=t[i],"function"==typeof e?this[i]=e:this["_"+i]=e;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hi(t,e,i,a){var s=C(),o=h().set(a,e);return s[i](o,t)}function ci(t,e,i,a,s){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return hi(t,e,i,s);var o,n=[];for(o=0;a>o;o++)n[o]=hi(t,o,i,s);return n}function ui(t,e){return ci(t,e,"months",12,"month")}function di(t,e){return ci(t,e,"monthsShort",12,"month")}function fi(t,e){return ci(t,e,"weekdays",7,"day")}function gi(t,e){return ci(t,e,"weekdaysShort",7,"day")}function mi(t,e){return ci(t,e,"weekdaysMin",7,"day")}function pi(){var t=this._data;return this._milliseconds=Ja(this._milliseconds),this._days=Ja(this._days),this._months=Ja(this._months),t.milliseconds=Ja(t.milliseconds),t.seconds=Ja(t.seconds),t.minutes=Ja(t.minutes),t.hours=Ja(t.hours),t.months=Ja(t.months),t.years=Ja(t.years),this}function bi(t,e,i,a){var s=$t(e,i);return t._milliseconds+=a*s._milliseconds,t._days+=a*s._days,t._months+=a*s._months,t._bubble()}function vi(t,e){return bi(this,t,e,1)}function xi(t,e){return bi(this,t,e,-1)}function yi(t){return 0>t?Math.floor(t):Math.ceil(t)}function ki(){var t,e,i,a,s,o=this._milliseconds,n=this._days,r=this._months,l=this._data;return o>=0&&n>=0&&r>=0||0>=o&&0>=n&&0>=r||(o+=864e5*yi(Di(r)+n),n=0,r=0),l.milliseconds=o%1e3,t=b(o/1e3),l.seconds=t%60,e=b(t/60),l.minutes=e%60,i=b(e/60),l.hours=i%24,n+=b(i/24),s=b(_i(n)),r+=s,n-=yi(Di(s)),a=b(r/12),r%=12,l.days=n,l.months=r,l.years=a,this}function _i(t){return 4800*t/146097}function Di(t){return 146097*t/4800}function Si(t){var e,i,a=this._milliseconds;if(t=A(t),"month"===t||"year"===t)return e=this._days+a/864e5,i=this._months+_i(e),"month"===t?i:i/12;switch(e=this._days+Math.round(Di(this._months)),t){case"week":return e/7+a/6048e5;case"day":return e+a/864e5;case"hour":return 24*e+a/36e5;case"minute":return 1440*e+a/6e4;case"second":return 86400*e+a/1e3;case"millisecond":return Math.floor(864e5*e)+a;default:throw new Error("Unknown unit "+t)}}function wi(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*v(this._months/12)}function Ci(t){return function(){return this.as(t)}}function Mi(t){return t=A(t),this[t+"s"]()}function Ai(t){return function(){return this._data[t]}}function Ii(){return b(this.days()/7)}function Ti(t,e,i,a,s){return s.relativeTime(e||1,!!i,t,a)}function Fi(t,e,i){var a=$t(t).abs(),s=ds(a.as("s")),o=ds(a.as("m")),n=ds(a.as("h")),r=ds(a.as("d")),l=ds(a.as("M")),h=ds(a.as("y")),c=s0,c[4]=i,Ti.apply(null,c)}function Pi(t,e){return void 0===fs[t]?!1:void 0===e?fs[t]:(fs[t]=e,!0)}function Oi(t){var e=this.localeData(),i=Fi(this,!t,e);return t&&(i=e.pastFuture(+this,i)),e.postformat(i)}function Vi(){var t,e,i,a=gs(this._milliseconds)/1e3,s=gs(this._days),o=gs(this._months);t=b(a/60),e=b(t/60),a%=60,t%=60,i=b(o/12),o%=12;var n=i,r=o,l=s,h=e,c=t,u=a,d=this.asSeconds();return d?(0>d?"-":"")+"P"+(n?n+"Y":"")+(r?r+"M":"")+(l?l+"D":"")+(h||c||u?"T":"")+(h?h+"H":"")+(c?c+"M":"")+(u?u+"S":""):"P0D"}var Wi,Li,Ri=i.momentProperties=[],zi=!1,Bi={},Yi={},Ni=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Hi=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ei={},Ui={},ji=/\d/,qi=/\d\d/,Gi=/\d{3}/,Zi=/\d{4}/,Qi=/[+-]?\d{6}/,Ji=/\d\d?/,Xi=/\d{1,3}/,$i=/\d{1,4}/,Ki=/[+-]?\d{1,6}/,ta=/\d+/,ea=/[+-]?\d+/,ia=/Z|[+-]\d\d:?\d\d/gi,aa=/[+-]?\d+(\.\d{1,3})?/,sa=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,oa={},na={},ra=0,la=1,ha=2,ca=3,ua=4,da=5,fa=6;W("M",["MM",2],"Mo",function(){return this.month()+1}),W("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),W("MMMM",0,0,function(t){return this.localeData().months(this,t)}),M("month","M"),N("M",Ji),N("MM",Ji,qi),N("MMM",sa),N("MMMM",sa),U(["M","MM"],function(t,e){e[la]=v(t)-1}),U(["MMM","MMMM"],function(t,e,i,a){var s=i._locale.monthsParse(t,a,i._strict);null!=s?e[la]=s:u(i).invalidMonth=t});var ga="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ma="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),pa={};i.suppressDeprecationWarnings=!1;var ba=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,va=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],xa=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ya=/^\/?Date\((\-?\d+)/i;i.createFromInputFallback=it("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),W(0,["YY",2],0,function(){return this.year()%100}),W(0,["YYYY",4],0,"year"),W(0,["YYYYY",5],0,"year"),W(0,["YYYYYY",6,!0],0,"year"),M("year","y"),N("Y",ea),N("YY",Ji,qi),N("YYYY",$i,Zi),N("YYYYY",Ki,Qi),N("YYYYYY",Ki,Qi),U(["YYYYY","YYYYYY"],ra),U("YYYY",function(t,e){e[ra]=2===t.length?i.parseTwoDigitYear(t):v(t)}),U("YY",function(t,e){e[ra]=i.parseTwoDigitYear(t)}),i.parseTwoDigitYear=function(t){return v(t)+(v(t)>68?1900:2e3)};var ka=T("FullYear",!1);W("w",["ww",2],"wo","week"),W("W",["WW",2],"Wo","isoWeek"),M("week","w"),M("isoWeek","W"),N("w",Ji),N("ww",Ji,qi),N("W",Ji),N("WW",Ji,qi),j(["w","ww","W","WW"],function(t,e,i,a){e[a.substr(0,1)]=v(t)});var _a={dow:0,doy:6};W("DDD",["DDDD",3],"DDDo","dayOfYear"),M("dayOfYear","DDD"),N("DDD",Xi),N("DDDD",Gi),U(["DDD","DDDD"],function(t,e,i){i._dayOfYear=v(t)}),i.ISO_8601=function(){};var Da=it("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Ft.apply(null,arguments);return this>t?this:t}),Sa=it("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Ft.apply(null,arguments);return t>this?this:t});Rt("Z",":"),Rt("ZZ",""),N("Z",ia),N("ZZ",ia),U(["Z","ZZ"],function(t,e,i){i._useUTC=!0,i._tzm=zt(t)});var wa=/([\+\-]|\d\d)/gi;i.updateOffset=function(){};var Ca=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Ma=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;$t.fn=Wt.prototype;var Aa=ie(1,"add"),Ia=ie(-1,"subtract");i.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Ta=it("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});W(0,["gg",2],0,function(){return this.weekYear()%100}),W(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Fe("gggg","weekYear"),Fe("ggggg","weekYear"),Fe("GGGG","isoWeekYear"),Fe("GGGGG","isoWeekYear"),M("weekYear","gg"),M("isoWeekYear","GG"),N("G",ea),N("g",ea),N("GG",Ji,qi),N("gg",Ji,qi),N("GGGG",$i,Zi),N("gggg",$i,Zi),N("GGGGG",Ki,Qi),N("ggggg",Ki,Qi),j(["gggg","ggggg","GGGG","GGGGG"],function(t,e,i,a){e[a.substr(0,2)]=v(t)}),j(["gg","GG"],function(t,e,a,s){e[s]=i.parseTwoDigitYear(t)}),W("Q",0,0,"quarter"),M("quarter","Q"),N("Q",ji),U("Q",function(t,e){e[la]=3*(v(t)-1)}),W("D",["DD",2],"Do","date"),M("date","D"),N("D",Ji),N("DD",Ji,qi),N("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),U(["D","DD"],ha),U("Do",function(t,e){e[ha]=v(t.match(Ji)[0],10)});var Fa=T("Date",!0);W("d",0,"do","day"),W("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),W("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),W("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),W("e",0,0,"weekday"),W("E",0,0,"isoWeekday"),M("day","d"),M("weekday","e"),M("isoWeekday","E"),N("d",Ji),N("e",Ji),N("E",Ji),N("dd",sa),N("ddd",sa),N("dddd",sa),j(["dd","ddd","dddd"],function(t,e,i){var a=i._locale.weekdaysParse(t);null!=a?e.d=a:u(i).invalidWeekday=t}),j(["d","e","E"],function(t,e,i,a){e[a]=v(t)});var Pa="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Oa="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Va="Su_Mo_Tu_We_Th_Fr_Sa".split("_");W("H",["HH",2],0,"hour"),W("h",["hh",2],0,function(){return this.hours()%12||12}),qe("a",!0),qe("A",!1),M("hour","h"),N("a",Ge),N("A",Ge),N("H",Ji),N("h",Ji),N("HH",Ji,qi),N("hh",Ji,qi),U(["H","HH"],ca),U(["a","A"],function(t,e,i){i._isPm=i._locale.isPM(t),i._meridiem=t}),U(["h","hh"],function(t,e,i){e[ca]=v(t),u(i).bigHour=!0});var Wa=/[ap]\.?m?\.?/i,La=T("Hours",!0);W("m",["mm",2],0,"minute"),M("minute","m"),N("m",Ji),N("mm",Ji,qi),U(["m","mm"],ua);var Ra=T("Minutes",!1);W("s",["ss",2],0,"second"),M("second","s"),N("s",Ji),N("ss",Ji,qi),U(["s","ss"],da);var za=T("Seconds",!1);W("S",0,0,function(){return~~(this.millisecond()/100)}),W(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),W(0,["SSS",3],0,"millisecond"),W(0,["SSSS",4],0,function(){return 10*this.millisecond()}),W(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),W(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),W(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),W(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),W(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),M("millisecond","ms"),N("S",Xi,ji),N("SS",Xi,qi),N("SSS",Xi,Gi);var Ba;for(Ba="SSSS";Ba.length<=9;Ba+="S")N(Ba,ta);for(Ba="S";Ba.length<=9;Ba+="S")U(Ba,Je);var Ya=T("Milliseconds",!1);W("z",0,0,"zoneAbbr"),W("zz",0,0,"zoneName");var Na=m.prototype;Na.add=Aa,Na.calendar=se,Na.clone=oe,Na.diff=ce,Na.endOf=_e,Na.format=ge,Na.from=me,Na.fromNow=pe,Na.to=be,Na.toNow=ve,Na.get=O,Na.invalidAt=Te,Na.isAfter=ne,Na.isBefore=re,Na.isBetween=le,Na.isSame=he,Na.isValid=Ae,Na.lang=Ta,Na.locale=xe,Na.localeData=ye,Na.max=Sa,Na.min=Da,Na.parsingFlags=Ie,Na.set=O,Na.startOf=ke,Na.subtract=Ia,Na.toArray=Ce,Na.toObject=Me,Na.toDate=we,Na.toISOString=fe,Na.toJSON=fe,Na.toString=de,Na.unix=Se,Na.valueOf=De,Na.year=ka,Na.isLeapYear=ct,Na.weekYear=Oe,Na.isoWeekYear=Ve,Na.quarter=Na.quarters=Re,Na.month=$,Na.daysInMonth=K,Na.week=Na.weeks=mt,Na.isoWeek=Na.isoWeeks=pt,Na.weeksInYear=Le,Na.isoWeeksInYear=We,Na.date=Fa,Na.day=Na.days=Ee,Na.weekday=Ue,Na.isoWeekday=je,Na.dayOfYear=vt,Na.hour=Na.hours=La,Na.minute=Na.minutes=Ra,Na.second=Na.seconds=za,Na.millisecond=Na.milliseconds=Ya, -Na.utcOffset=Nt,Na.utc=Et,Na.local=Ut,Na.parseZone=jt,Na.hasAlignedHourOffset=qt,Na.isDST=Gt,Na.isDSTShifted=Zt,Na.isLocal=Qt,Na.isUtcOffset=Jt,Na.isUtc=Xt,Na.isUTC=Xt,Na.zoneAbbr=Xe,Na.zoneName=$e,Na.dates=it("dates accessor is deprecated. Use date instead.",Fa),Na.months=it("months accessor is deprecated. Use month instead",$),Na.years=it("years accessor is deprecated. Use year instead",ka),Na.zone=it("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ht);var Ha=Na,Ea={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Ua={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},ja="Invalid date",qa="%d",Ga=/\d{1,2}/,Za={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Qa=y.prototype;Qa._calendar=Ea,Qa.calendar=ei,Qa._longDateFormat=Ua,Qa.longDateFormat=ii,Qa._invalidDate=ja,Qa.invalidDate=ai,Qa._ordinal=qa,Qa.ordinal=si,Qa._ordinalParse=Ga,Qa.preparse=oi,Qa.postformat=oi,Qa._relativeTime=Za,Qa.relativeTime=ni,Qa.pastFuture=ri,Qa.set=li,Qa.months=Z,Qa._months=ga,Qa.monthsShort=Q,Qa._monthsShort=ma,Qa.monthsParse=J,Qa.week=dt,Qa._week=_a,Qa.firstDayOfYear=gt,Qa.firstDayOfWeek=ft,Qa.weekdays=Be,Qa._weekdays=Pa,Qa.weekdaysMin=Ne,Qa._weekdaysMin=Va,Qa.weekdaysShort=Ye,Qa._weekdaysShort=Oa,Qa.weekdaysParse=He,Qa.isPM=Ze,Qa._meridiemParse=Wa,Qa.meridiem=Qe,S("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,i=1===v(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+i}}),i.lang=it("moment.lang is deprecated. Use moment.locale instead.",S),i.langData=it("moment.langData is deprecated. Use moment.localeData instead.",C);var Ja=Math.abs,Xa=Ci("ms"),$a=Ci("s"),Ka=Ci("m"),ts=Ci("h"),es=Ci("d"),is=Ci("w"),as=Ci("M"),ss=Ci("y"),os=Ai("milliseconds"),ns=Ai("seconds"),rs=Ai("minutes"),ls=Ai("hours"),hs=Ai("days"),cs=Ai("months"),us=Ai("years"),ds=Math.round,fs={s:45,m:45,h:22,d:26,M:11},gs=Math.abs,ms=Wt.prototype;ms.abs=pi,ms.add=vi,ms.subtract=xi,ms.as=Si,ms.asMilliseconds=Xa,ms.asSeconds=$a,ms.asMinutes=Ka,ms.asHours=ts,ms.asDays=es,ms.asWeeks=is,ms.asMonths=as,ms.asYears=ss,ms.valueOf=wi,ms._bubble=ki,ms.get=Mi,ms.milliseconds=os,ms.seconds=ns,ms.minutes=rs,ms.hours=ls,ms.days=hs,ms.weeks=Ii,ms.months=cs,ms.years=us,ms.humanize=Oi,ms.toISOString=Vi,ms.toString=Vi,ms.toJSON=Vi,ms.locale=xe,ms.localeData=ye,ms.toIsoString=it("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Vi),ms.lang=Ta,W("X",0,0,"unix"),W("x",0,0,"valueOf"),N("x",ea),N("X",aa),U("X",function(t,e,i){i._d=new Date(1e3*parseFloat(t,10))}),U("x",function(t,e,i){i._d=new Date(v(t))}),i.version="2.10.6",a(Ft),i.fn=Ha,i.min=Ot,i.max=Vt,i.utc=h,i.unix=Ke,i.months=ui,i.isDate=o,i.locale=S,i.invalid=f,i.duration=$t,i.isMoment=p,i.weekdays=fi,i.parseZone=ti,i.localeData=C,i.isDuration=Lt,i.monthsShort=di,i.weekdaysMin=mi,i.defineLocale=w,i.weekdaysShort=gi,i.normalizeUnits=A,i.relativeTimeThreshold=Pi;var ps=i;return ps})},{}],7:[function(t,e,i){/*! +/*! * Chart.js * http://chartjs.org/ - * Version: 2.0.2 + * Version: 2.1.3 * - * Copyright 2015 Nick Downie + * Copyright 2016 Nick Downie * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md */ -var a=t("./core/core.js")();t("./core/core.helpers")(a),t("./core/core.element")(a),t("./core/core.animation")(a),t("./core/core.controller")(a),t("./core/core.datasetController")(a),t("./core/core.layoutService")(a),t("./core/core.legend")(a),t("./core/core.scale")(a),t("./core/core.scaleService")(a),t("./core/core.title")(a),t("./core/core.tooltip")(a),t("./controllers/controller.bar")(a),t("./controllers/controller.bubble")(a),t("./controllers/controller.doughnut")(a),t("./controllers/controller.line")(a),t("./controllers/controller.polarArea")(a),t("./controllers/controller.radar")(a),t("./scales/scale.category")(a),t("./scales/scale.linear")(a),t("./scales/scale.logarithmic")(a),t("./scales/scale.radialLinear")(a),t("./scales/scale.time")(a),t("./elements/element.arc")(a),t("./elements/element.line")(a),t("./elements/element.point")(a),t("./elements/element.rectangle")(a),t("./charts/Chart.Bar")(a),t("./charts/Chart.Bubble")(a),t("./charts/Chart.Doughnut")(a),t("./charts/Chart.Line")(a),t("./charts/Chart.PolarArea")(a),t("./charts/Chart.Radar")(a),t("./charts/Chart.Scatter")(a),window.Chart=e.exports=a},{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.scale":29,"./core/core.scaleService":30,"./core/core.title":31,"./core/core.tooltip":32,"./elements/element.arc":33,"./elements/element.line":34,"./elements/element.point":35,"./elements/element.rectangle":36,"./scales/scale.category":37,"./scales/scale.linear":38,"./scales/scale.logarithmic":39,"./scales/scale.radialLinear":40,"./scales/scale.time":41}],8:[function(t,e,i){"use strict";e.exports=function(t){t.Bar=function(e,i){return i.type="bar",new t(e,i)}}},{}],9:[function(t,e,i){"use strict";e.exports=function(t){t.Bubble=function(e,i){return i.type="bubble",new t(e,i)}}},{}],10:[function(t,e,i){"use strict";e.exports=function(t){t.Doughnut=function(e,i){return i.type="doughnut",new t(e,i)}}},{}],11:[function(t,e,i){"use strict";e.exports=function(t){t.Line=function(e,i){return i.type="line",new t(e,i)}}},{}],12:[function(t,e,i){"use strict";e.exports=function(t){t.PolarArea=function(e,i){return i.type="polarArea",new t(e,i)}}},{}],13:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={aspectRatio:1};t.Radar=function(a,s){return s.options=e.configMerge(i,s.options),s.type="radar",new t(a,s)}}},{}],14:[function(t,e,i){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,i){return i.type="scatter",new t(e,i)}}},{}],15:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({initialize:function(e,i){t.DatasetController.prototype.initialize.call(this,e,i),this.getDataset().bar=!0},getBarCount:function(){var t=0;return e.each(this.chart.data.datasets,function(i){e.isDatasetVisible(i)&&i.bar&&++t}),t},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:e}),a=this.getBarCount();this.updateElement(i,e,!0,a),this.getDataset().metaData.splice(e,0,i)},update:function(t){var i=this.getBarCount();e.each(this.getDataset().metaData,function(e,a){this.updateElement(e,a,t,i)},this)},updateElement:function(t,i,a,s){var o,n=this.getScaleForId(this.getDataset().xAxisID),r=this.getScaleForId(this.getDataset().yAxisID);o=r.min<0&&r.max<0?r.getPixelForValue(r.max):r.min>0&&r.max>0?r.getPixelForValue(r.min):r.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:n,_yScale:r,_datasetIndex:this.index,_index:i,_model:{x:this.calculateBarX(i,this.index),y:a?o:this.calculateBarY(i,this.index),label:this.chart.data.labels[i],datasetLabel:this.getDataset().label,base:a?o:this.calculateBarBase(this.index,i),width:this.calculateBarWidth(s),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),borderSkipped:t.custom&&t.custom.borderSkipped?t.custom.borderSkipped:this.chart.options.elements.rectangle.borderSkipped,borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}}),t.pivot()},calculateBarBase:function(t,i){var a=(this.getScaleForId(this.getDataset().xAxisID),this.getScaleForId(this.getDataset().yAxisID)),s=0;if(a.options.stacked){var o=this.chart.data.datasets[t].data[i];if(0>o)for(var n=0;t>n;n++){var r=this.chart.data.datasets[n];e.isDatasetVisible(r)&&r.yAxisID===a.id&&r.bar&&(s+=r.data[i]<0?r.data[i]:0)}else for(var l=0;t>l;l++){var h=this.chart.data.datasets[l];e.isDatasetVisible(h)&&h.yAxisID===a.id&&h.bar&&(s+=h.data[i]>0?h.data[i]:0)}return a.getPixelForValue(s)}return s=a.getPixelForValue(a.min),a.beginAtZero||a.min<=0&&a.max>=0||a.min>=0&&a.max<=0?s=a.getPixelForValue(0,0):a.min<0&&a.max<0&&(s=a.getPixelForValue(a.max)),s},getRuler:function(){var t=this.getScaleForId(this.getDataset().xAxisID),e=(this.getScaleForId(this.getDataset().yAxisID),this.getBarCount()),i=function(){for(var e=t.getPixelForTick(1)-t.getPixelForTick(0),i=2;ia;++a)e.isDatasetVisible(this.chart.data.datasets[a])&&this.chart.data.datasets[a].bar&&++i;return i},calculateBarX:function(t,e){var i=(this.getScaleForId(this.getDataset().yAxisID),this.getScaleForId(this.getDataset().xAxisID)),a=this.getBarIndex(e),s=this.getRuler(),o=i.getPixelForValue(null,t,e,this.chart.isCombo);return o-=this.chart.isCombo?s.tickWidth/2:0,i.options.stacked?o+s.categoryWidth/2+s.categorySpacing:o+s.barWidth/2+s.categorySpacing+s.barWidth*a+s.barSpacing/2+s.barSpacing*a},calculateBarY:function(t,i){var a=(this.getScaleForId(this.getDataset().xAxisID),this.getScaleForId(this.getDataset().yAxisID)),s=this.getDataset().data[t];if(a.options.stacked){for(var o=0,n=0,r=0;i>r;r++){var l=this.chart.data.datasets[r];e.isDatasetVisible(l)&&l.bar&&l.yAxisID===a.id&&(l.data[t]<0?n+=l.data[t]||0:o+=l.data[t]||0)}return 0>s?a.getPixelForValue(n+s):a.getPixelForValue(o+s)}return a.getPixelForValue(s)},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){var a=this.getDataset().data[e];null===a||void 0===a||isNaN(a)||t.transition(i).draw()},this)},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}})}},{}],16:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){var i=e.datasets[t.datasetIndex].label||"",a=e.datasets[t.datasetIndex].data[t.index];return i+": ("+a.x+", "+a.y+", "+a.r+")"}}}},t.controllers.bubble=t.DatasetController.extend({addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i)},update:function(t){var i,a=this.getDataset().metaData,s=this.getScaleForId(this.getDataset().yAxisID);this.getScaleForId(this.getDataset().xAxisID);i=s.min<0&&s.max<0?s.getPixelForValue(s.max):s.min>0&&s.max>0?s.getPixelForValue(s.min):s.getPixelForValue(0),e.each(a,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,a){var s,o=this.getScaleForId(this.getDataset().yAxisID),n=this.getScaleForId(this.getDataset().xAxisID);s=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:n,_yScale:o,_datasetIndex:this.index,_index:i,_model:{x:a?n.getPixelForDecimal(.5):n.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:a?s:o.getPixelForValue(this.getDataset().data[i],i,this.index),radius:a?0:t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[i]),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y),t.pivot()},getRadius:function(t){return t.r||this.chart.options.elements.point.radius},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i),t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.hoverRadius,a,this.chart.options.elements.point.hoverRadius)+this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],17:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.doughnut={animation:{animateRotate:!0,animateScale:!1},aspectRatio:1,hover:{mode:"single"},legendCallback:function(t){var e=[];if(e.push('
    '),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){return t.labels.length&&t.datasets.length?t.labels.map(function(e,i){return{text:e,fillStyle:t.datasets[0].backgroundColor[i],hidden:isNaN(t.datasets[0].data[i]),index:i}}):[]}},onClick:function(t,i){e.each(this.chart.data.datasets,function(t){t.metaHiddenData=t.metaHiddenData||[];var e=i.index;isNaN(t.data[e])?isNaN(t.metaHiddenData[e])||(t.data[e]=t.metaHiddenData[e]):(t.metaHiddenData[e]=t.data[e],t.data[e]=NaN)}),this.chart.update()}},cutoutPercentage:50,rotation:Math.PI*-.5,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+e.datasets[t.datasetIndex].data[t.index]}}}},t.defaults.pie=e.clone(t.defaults.doughnut),e.extend(t.defaults.pie,{cutoutPercentage:0}),t.controllers.doughnut=t.controllers.pie=t.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(i,a){this.getDataset().metaData=this.getDataset().metaData||[];var s=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i});a&&e.isArray(this.getDataset().backgroundColor)&&this.getDataset().backgroundColor.splice(i,0,a),this.updateElement(s,i,!0),this.getDataset().metaData.splice(i,0,s)},getVisibleDatasetCount:function(){return e.where(this.chart.data.datasets,function(t){return e.isDatasetVisible(t)}).length},getRingIndex:function(t){for(var i=0,a=0;t>a;++a)e.isDatasetVisible(this.chart.data.datasets[a])&&++i;return i},update:function(t){var i=this.chart.chartArea.right-this.chart.chartArea.left-this.chart.options.elements.arc.borderWidth,a=this.chart.chartArea.bottom-this.chart.chartArea.top-this.chart.options.elements.arc.borderWidth,s=Math.min(i,a),o={x:0,y:0};if(this.chart.options.circumference&&this.chart.options.circumference<2*Math.PI){var n=this.chart.options.rotation%(2*Math.PI);n+=2*Math.PI*(n>=Math.PI?-1:n<-Math.PI?1:0);var r=n+this.chart.options.circumference,l={x:Math.cos(n),y:Math.sin(n)},h={x:Math.cos(r),y:Math.sin(r)},c=0>=n&&r>=0||n<=2*Math.PI&&2*Math.PI<=r,u=n<=.5*Math.PI&&.5*Math.PI<=r||n<=2.5*Math.PI&&2.5*Math.PI<=r,d=n<=-Math.PI&&-Math.PI<=r||n<=Math.PI&&Math.PI<=r,f=n<=.5*-Math.PI&&.5*-Math.PI<=r||n<=1.5*Math.PI&&1.5*Math.PI<=r,g=this.chart.options.cutoutPercentage/100,m={x:d?-1:Math.min(l.x*(l.x<0?1:g),h.x*(h.x<0?1:g)),y:f?-1:Math.min(l.y*(l.y<0?1:g),h.y*(h.y<0?1:g))},p={x:c?1:Math.max(l.x*(l.x>0?1:g),h.x*(h.x>0?1:g)),y:u?1:Math.max(l.y*(l.y>0?1:g),h.y*(h.y>0?1:g))},b={width:.5*(p.x-m.x),height:.5*(p.y-m.y)};s=Math.min(i/b.width,a/b.height),o={x:(p.x+m.x)*-.5,y:(p.y+m.y)*-.5}}this.chart.outerRadius=Math.max(s/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.getVisibleDatasetCount(),this.chart.offsetX=o.x*this.chart.outerRadius,this.chart.offsetY=o.y*this.chart.outerRadius,this.getDataset().total=0,e.each(this.getDataset().data,function(t){isNaN(t)||(this.getDataset().total+=Math.abs(t))},this),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.getRingIndex(this.index),this.innerRadius=this.outerRadius-this.chart.radiusLength,e.each(this.getDataset().metaData,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,a){var s=(this.chart.chartArea.left+this.chart.chartArea.right)/2,o=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,n=this.chart.options.rotation||Math.PI*-.5,r=this.chart.options.rotation||Math.PI*-.5,l=a&&this.chart.options.animation.animateRotate?0:this.calculateCircumference(this.getDataset().data[i])*((this.chart.options.circumference||2*Math.PI)/(2*Math.PI)),h=a&&this.chart.options.animation.animateScale?0:this.innerRadius,c=a&&this.chart.options.animation.animateScale?0:this.outerRadius;e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_model:{x:s+this.chart.offsetX,y:o+this.chart.offsetY,startAngle:n,endAngle:r,circumference:l,outerRadius:c,innerRadius:h,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor,i,this.chart.options.elements.arc.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.getDataset().label,i,this.chart.data.labels[i])}}),a||(0===i?t._model.startAngle=this.chart.options.rotation||Math.PI*-.5:t._model.startAngle=this.getDataset().metaData[i-1]._model.endAngle,t._model.endAngle=t._model.startAngle+t._model.circumference),t.pivot()},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},calculateCircumference:function(t){return this.getDataset().total>0&&!isNaN(t)?1.999999*Math.PI*(t/this.getDataset().total):0}})}},{}],18:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.line={showLines:!0,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],this.getDataset().metaDataset=this.getDataset().metaDataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:this.getDataset().metaData}),e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},update:function(t){var i,a=this.getDataset().metaDataset,s=this.getDataset().metaData,o=this.getScaleForId(this.getDataset().yAxisID);this.getScaleForId(this.getDataset().xAxisID);i=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),this.chart.options.showLines&&(a._scale=o,a._datasetIndex=this.index,a._children=s,a._model={tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,scaleTop:o.top,scaleBottom:o.bottom,scaleZero:i},a.pivot()),e.each(s,function(e,i){this.updateElement(e,i,t)},this),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},getPointBackgroundColor:function(t,i){var a=this.chart.options.elements.point.backgroundColor,s=this.getDataset();return t.custom&&t.custom.backgroundColor?a=t.custom.backgroundColor:s.pointBackgroundColor?a=e.getValueAtIndexOrDefault(s.pointBackgroundColor,i,a):s.backgroundColor&&(a=s.backgroundColor),a},getPointBorderColor:function(t,i){var a=this.chart.options.elements.point.borderColor,s=this.getDataset();return t.custom&&t.custom.borderColor?a=t.custom.borderColor:s.pointBorderColor?a=e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,a):s.borderColor&&(a=s.borderColor),a},getPointBorderWidth:function(t,i){var a=this.chart.options.elements.point.borderWidth,s=this.getDataset();return t.custom&&void 0!==t.custom.borderWidth?a=t.custom.borderWidth:void 0!==s.pointBorderWidth?a=e.getValueAtIndexOrDefault(s.pointBorderWidth,i,a):void 0!==s.borderWidth&&(a=s.borderWidth),a},updateElement:function(t,i,a){var s,o=this.getScaleForId(this.getDataset().yAxisID),n=this.getScaleForId(this.getDataset().xAxisID);s=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),t._chart=this.chart.chart,t._xScale=n,t._yScale=o,t._datasetIndex=this.index,t._index=i,t._model={x:n.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:a?s:this.calculatePointY(this.getDataset().data[i],i,this.index,this.chart.isCombo),tension:t.custom&&t.custom.tension?t.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),backgroundColor:this.getPointBackgroundColor(t,i),borderColor:this.getPointBorderColor(t,i),borderWidth:this.getPointBorderWidth(t,i),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)},t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},calculatePointY:function(t,i,a,s){var o=(this.getScaleForId(this.getDataset().xAxisID),this.getScaleForId(this.getDataset().yAxisID));if(o.options.stacked){for(var n=0,r=0,l=0;a>l;l++){var h=this.chart.data.datasets[l];"line"===h.type&&e.isDatasetVisible(h)&&(h.data[i]<0?r+=h.data[i]||0:n+=h.data[i]||0)}return 0>t?o.getPixelForValue(r+t):o.getPixelForValue(n+t)}return o.getPixelForValue(t)},updateBezierControlPoints:function(){e.each(this.getDataset().metaData,function(t,i){var a=e.splineCurve(e.previousItem(this.getDataset().metaData,i)._model,t._model,e.nextItem(this.getDataset().metaData,i)._model,t._model.tension);t._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t.pivot()},this)},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t){t.transition(i)}),this.chart.options.showLines&&this.getDataset().metaDataset.transition(i).draw(),e.each(this.getDataset().metaData,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=this.getPointBackgroundColor(t,i),t._model.borderColor=this.getPointBorderColor(t,i),t._model.borderWidth=this.getPointBorderWidth(t,i)}})}},{}],19:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0},animateRotate:!0,animateScale:!0,aspectRatio:1,legendCallback:function(t){var e=[];if(e.push('
    '),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){return t.labels.length&&t.datasets.length?t.labels.map(function(e,i){return{text:e,fillStyle:t.datasets[0].backgroundColor[i],hidden:isNaN(t.datasets[0].data[i]),index:i}}):[]}},onClick:function(t,i){e.each(this.chart.data.datasets,function(t){t.metaHiddenData=t.metaHiddenData||[];var e=i.index;isNaN(t.data[e])?isNaN(t.metaHiddenData[e])||(t.data[e]=t.metaHiddenData[e]):(t.metaHiddenData[e]=t.data[e],t.data[e]=NaN)}),this.chart.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}},t.controllers.polarArea=t.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i)},getVisibleDatasetCount:function(){return e.where(this.chart.data.datasets,function(t){return e.isDatasetVisible(t)}).length},update:function(t){var i=Math.min(this.chart.chartArea.right-this.chart.chartArea.left,this.chart.chartArea.bottom-this.chart.chartArea.top);this.chart.outerRadius=Math.max((i-this.chart.options.elements.arc.borderWidth/2)/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.getVisibleDatasetCount(),this.getDataset().total=0,e.each(this.getDataset().data,function(t){this.getDataset().total+=Math.abs(t)},this),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.index,this.innerRadius=this.outerRadius-this.chart.radiusLength,e.each(this.getDataset().metaData,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,a){for(var s=this.calculateCircumference(this.getDataset().data[i]),o=(this.chart.chartArea.left+this.chart.chartArea.right)/2,n=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,r=0,l=0;i>l;++l)isNaN(this.getDataset().data[l])||++r; -var h=-.5*Math.PI+s*r,c=h+s,u={x:o,y:n,innerRadius:0,outerRadius:this.chart.options.animateScale?0:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),startAngle:this.chart.options.animateRotate?Math.PI*-.5:h,endAngle:this.chart.options.animateRotate?Math.PI*-.5:c,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])};e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:a?u:{x:o,y:n,innerRadius:0,outerRadius:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),startAngle:h,endAngle:c,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])}}),t.pivot()},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},calculateCircumference:function(t){if(isNaN(t))return 0;var i=e.where(this.getDataset().data,function(t){return isNaN(t)}).length;return 2*Math.PI/(this.getDataset().data.length-i)}})}},{}],20:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],this.getDataset().metaDataset=this.getDataset().metaDataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:this.getDataset().metaData,_loop:!0}),e.each(this.getDataset().data,function(e,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_model:{x:0,y:0}})},this)},addElementAndReset:function(e){this.getDataset().metaData=this.getDataset().metaData||[];var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.updateElement(i,e,!0),this.getDataset().metaData.splice(e,0,i),this.updateBezierControlPoints()},update:function(t){var i,a=this.getDataset().metaDataset,s=this.getDataset().metaData,o=this.chart.scale;i=o.min<0&&o.max<0?o.getPointPositionForValue(0,o.max):o.min>0&&o.max>0?o.getPointPositionForValue(0,o.min):o.getPointPositionForValue(0,0),e.extend(this.getDataset().metaDataset,{_datasetIndex:this.index,_children:this.getDataset().metaData,_model:{tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,scaleTop:o.top,scaleBottom:o.bottom,scaleZero:i}}),this.getDataset().metaDataset.pivot(),e.each(s,function(e,i){this.updateElement(e,i,t)},this),this.updateBezierControlPoints()},updateElement:function(t,i,a){var s=this.chart.scale.getPointPositionForValue(i,this.getDataset().data[i]);e.extend(t,{_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:{x:a?this.chart.scale.xCenter:s.x,y:a?this.chart.scale.yCenter:s.y,tension:t.custom&&t.custom.tension?t.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){e.each(this.getDataset().metaData,function(t,i){var a=e.splineCurve(e.previousItem(this.getDataset().metaData,i,!0)._model,t._model,e.nextItem(this.getDataset().metaData,i,!0)._model,t._model.tension);t._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),t._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),t.pivot()},this)},draw:function(t){var i=t||1;e.each(this.getDataset().metaData,function(t,e){t.transition(i)}),this.getDataset().metaDataset.transition(i).draw(),e.each(this.getDataset().metaData,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],a=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,a,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,a,e.color(t._model.backgroundColor).saturate(.5).darken(.1).rgbString()),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,a,e.color(t._model.borderColor).saturate(.5).darken(.1).rgbString()),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,a,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],21:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,i,a){a||(t.animating=!0);for(var s=0;s1&&(e=Math.floor(this.dropFrames),this.dropFrames=this.dropFrames%1);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.onAnimationProgress&&this.animations[i].animationObject.onAnimationProgress.call&&this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].animationObject.currentStep===this.animations[i].animationObject.numSteps?(this.animations[i].animationObject.onAnimationComplete&&this.animations[i].animationObject.onAnimationComplete.call&&this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1)):++i;var a=Date.now(),s=(a-t)/this.frameDuration;this.dropFrames+=s,this.animations.length>0&&this.requestAnimationFrame()}}}},{}],22:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.types={},t.instances={},t.controllers={},t.Controller=function(i){return this.chart=i,this.config=i.config,this.options=this.config.options=e.configMerge(t.defaults.global,t.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Object.defineProperty(this,"data",{get:function(){return this.config.data}}),t.instances[this.id]=this,this.options.responsive&&this.resize(!0),this.initialize(),this},e.extend(t.Controller.prototype,{initialize:function(){return this.bindEvents(),this.ensureScalesHaveIDs(),this.buildOrUpdateControllers(),this.buildScales(),this.buildSurroundingItems(),this.updateLayout(),this.resetElements(),this.initToolTip(),this.update(),this},clear:function(){return e.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var i=this.chart.canvas,a=e.getMaximumWidth(this.chart.canvas),s=this.options.maintainAspectRatio&&isNaN(this.chart.aspectRatio)===!1&&isFinite(this.chart.aspectRatio)&&0!==this.chart.aspectRatio?a/this.chart.aspectRatio:e.getMaximumHeight(this.chart.canvas),o=this.chart.width!==a||this.chart.height!==s;return o?(i.width=this.chart.width=a,i.height=this.chart.height=s,e.retinaScale(this.chart),t||(this.stop(),this.update(this.options.responsiveAnimationDuration)),this):this},ensureScalesHaveIDs:function(){var t="x-axis-",i="y-axis-";this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(e,i){e.id=e.id||t+i}),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(t,e){t.id=t.id||i+e}))},buildScales:function(){if(this.scales={},this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(i,a){var s=e.getValueOrDefault(i.type,"category"),o=t.scaleService.getScaleConstructor(s);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(i,a){var s=e.getValueOrDefault(i.type,"linear"),o=t.scaleService.getScaleConstructor(s);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this)),this.options.scale){var i=t.scaleService.getScaleConstructor(this.options.scale.type);if(i){var a=new i({ctx:this.chart.ctx,options:this.options.scale,chart:this});this.scale=a,this.scales.radialScale=a}}t.scaleService.addScalesToLayout(this)},buildSurroundingItems:function(){this.options.title&&(this.titleBlock=new t.Title({ctx:this.chart.ctx,options:this.options.title,chart:this}),t.layoutService.addBox(this,this.titleBlock)),this.options.legend&&(this.legend=new t.Legend({ctx:this.chart.ctx,options:this.options.legend,chart:this}),t.layoutService.addBox(this,this.legend))},updateLayout:function(){t.layoutService.update(this,this.chart.width,this.chart.height)},buildOrUpdateControllers:function(){var i=[],a=[];if(e.each(this.data.datasets,function(e,s){e.type||(e.type=this.config.type);var o=e.type;i.push(o),e.controller?e.controller.updateIndex(s):(e.controller=new t.controllers[o](this,s),a.push(e.controller))},this),i.length>1)for(var s=1;s0&&(e=this.data.datasets[e[0]._datasetIndex].metaData),e},generateLegend:function(){return this.options.legendCallback(this)},destroy:function(){this.clear(),e.unbindEvents(this,this.events),e.removeResizeListener(this.chart.canvas.parentNode);var i=this.chart.canvas;i.width=this.chart.width,i.height=this.chart.height,void 0!==this.chart.originalDevicePixelRatio&&this.chart.ctx.scale(1/this.chart.originalDevicePixelRatio,1/this.chart.originalDevicePixelRatio),i.style.width=this.chart.originalCanvasStyleWidth,i.style.height=this.chart.originalCanvasStyleHeight,delete t.instances[this.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)},initToolTip:function(){this.tooltip=new t.Tooltip({_chart:this.chart,_chartInstance:this,_data:this.data,_options:this.options},this)},bindEvents:function(){e.bindEvents(this,this.options.events,function(t){this.eventHandler(t)})},eventHandler:function(t){if(this.lastActive=this.lastActive||[],this.lastTooltipActive=this.lastTooltipActive||[],"mouseout"===t.type)this.active=[],this.tooltipActive=[];else{var i=this,a=function(e){switch(e){case"single":return i.getElementAtEvent(t);case"label":return i.getElementsAtEvent(t);case"dataset":return i.getDatasetAtEvent(t);default:return t}};this.active=a(this.options.hover.mode),this.tooltipActive=a(this.options.tooltips.mode)}this.options.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"===t.type||"click"===t.type)&&(this.options.onClick&&this.options.onClick.call(this,t,this.active),this.legend&&this.legend.handleEvent&&this.legend.handleEvent(t));if(this.lastActive.length)switch(this.options.hover.mode){case"single":this.data.datasets[this.lastActive[0]._datasetIndex].controller.removeHoverStyle(this.lastActive[0],this.lastActive[0]._datasetIndex,this.lastActive[0]._index);break;case"label":case"dataset":for(var s=0;st)this.getDataset().metaData.splice(t,e-t);else if(t>e)for(var i=e;t>i;++i)this.addElementAndReset(i)},addElements:e.noop,addElementAndReset:e.noop,draw:e.noop,removeHoverStyle:e.noop,setHoverStyle:e.noop,update:e.noop}),t.DatasetController.extend=e.inherits}},{}],24:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.elements={},t.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(t.Element.prototype,{initialize:function(){},pivot:function(){return this._view||(this._view=e.clone(this._model)),this._start=e.clone(this._view),this},transition:function(t){return this._view||(this._view=e.clone(this._model)),1===t?(this._view=this._model,this._start=null,this):(this._start||this.pivot(),e.each(this._model,function(i,a){if("_"!==a[0]&&this._model.hasOwnProperty(a))if(this._view.hasOwnProperty(a))if(i===this._view[a]);else if("string"==typeof i)try{var s=e.color(this._start[a]).mix(e.color(this._model[a]),t);this._view[a]=s.rgbString()}catch(o){this._view[a]=i}else if("number"==typeof i){var n=void 0!==this._start[a]&&isNaN(this._start[a])===!1?this._start[a]:0;this._view[a]=(this._model[a]-n)*t+n}else this._view[a]=i;else"number"!=typeof i||isNaN(this._view[a])?this._view[a]=i:this._view[a]=i*t;else;},this),this)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),t.Element.extend=e.inherits}},{}],25:[function(t,e,i){"use strict";var a=t("chartjs-color");e.exports=function(t){function e(t,e,i){var a;return"string"==typeof t?(a=parseInt(t,10),-1!=t.indexOf("%")&&(a=a/100*e.parentNode[i])):a=t,a}function i(t,i,a){var s,o=document.defaultView.getComputedStyle(t)[i],n=document.defaultView.getComputedStyle(t.parentNode)[i],r=null!==o&&"none"!==o,l=null!==n&&"none"!==n;return(r||l)&&(s=Math.min(r?e(o,t,a):Number.POSITIVE_INFINITY,l?e(n,t.parentNode,a):Number.POSITIVE_INFINITY)),s}var s=t.helpers={};s.each=function(t,e,i,a){var o,n;if(s.isArray(t))if(n=t.length,a)for(o=n-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;n>o;o++)e.call(i,t[o],o);else if("object"==typeof t){var r=Object.keys(t);for(n=r.length,o=0;n>o;o++)e.call(i,t[r[o]],r[o])}},s.clone=function(t){var e={};return s.each(t,function(i,a){t.hasOwnProperty(a)&&(s.isArray(i)?e[a]=i.slice(0):"object"==typeof i&&null!==i?e[a]=s.clone(i):e[a]=i)}),e},s.extend=function(t){for(var e=arguments.length,i=[],a=1;e>a;a++)i.push(arguments[a]);return s.each(i,function(e){s.each(e,function(i,a){e.hasOwnProperty(a)&&(t[a]=i)})}),t},s.configMerge=function(e){var i=s.clone(e);return s.each(Array.prototype.slice.call(arguments,1),function(e){s.each(e,function(a,o){if(e.hasOwnProperty(o))if("scales"===o)i[o]=s.scaleMerge(i.hasOwnProperty(o)?i[o]:{},a);else if("scale"===o)i[o]=s.configMerge(i.hasOwnProperty(o)?i[o]:{},t.scaleService.getScaleDefaults(a.type),a);else if(i.hasOwnProperty(o)&&s.isArray(i[o])&&s.isArray(a)){var n=i[o];s.each(a,function(t,e){e=a[o].length||!a[o][i].type?a[o].push(s.configMerge(r,e)):e.type&&e.type!==a[o][i].type?a[o][i]=s.configMerge(a[o][i],r,e):a[o][i]=s.configMerge(a[o][i],e)}):(a[o]=[],s.each(e,function(e){var i=s.getValueOrDefault(e.type,"xAxes"===o?"category":"linear");a[o].push(s.configMerge(t.scaleService.getScaleDefaults(i),e))})):a.hasOwnProperty(o)&&"object"==typeof a[o]&&null!==a[o]&&"object"==typeof e?a[o]=s.configMerge(a[o],e):a[o]=e)}),a},s.getValueAtIndexOrDefault=function(t,e,i){return void 0===t||null===t?i:s.isArray(t)?e=0;a--){var s=t[a];if(e(s))return s}},s.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},a=function(){this.constructor=i};return a.prototype=e.prototype,i.prototype=new a,i.extend=s.inherits,t&&s.extend(i.prototype,t),i.__super__=e.prototype,i},s.noop=function(){},s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),s.warn=function(t){console&&"function"==typeof console.warn&&console.warn(t)},s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},s.almostEquals=function(t,e,i){return Math.abs(t-e)0?1:-1)},s.log10=function(t){return Math.log10?Math.log10(t):Math.log(t)/Math.LN10},s.toRadians=function(t){return t*(Math.PI/180)},s.toDegrees=function(t){return t*(180/Math.PI)},s.getAngleFromPoint=function(t,e){var i=e.x-t.x,a=e.y-t.y,s=Math.sqrt(i*i+a*a),o=Math.atan2(a,i);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:s}},s.aliasPixel=function(t){return t%2===0?0:.5},s.splineCurve=function(t,e,i,a){var s=t.skip?e:t,o=e,n=i.skip?e:i,r=Math.sqrt(Math.pow(o.x-s.x,2)+Math.pow(o.y-s.y,2)),l=Math.sqrt(Math.pow(n.x-o.x,2)+Math.pow(n.y-o.y,2)),h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;var u=a*h,d=a*c;return{previous:{x:o.x-u*(n.x-s.x),y:o.y-u*(n.y-s.y)},next:{x:o.x+d*(n.x-s.x),y:o.y+d*(n.y-s.y)}}},s.nextItem=function(t,e,i){return i?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},s.previousItem=function(t,e,i){return i?0>=e?t[t.length-1]:t[e-1]:0>=e?t[0]:t[e-1]},s.niceNum=function(t,e){var i,a=Math.floor(s.log10(t)),o=t/Math.pow(10,a);return i=e?1.5>o?1:3>o?2:7>o?5:10:1>=o?1:2>=o?2:5>=o?5:10,i*Math.pow(10,a)};var o=s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,a=1;return 0===t?0:1===(t/=1)?1:(i||(i=.3),at?-.5*(a*Math.pow(2,10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)):a*Math.pow(2,-10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-o.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?1*(7.5625*t*t):2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*o.easeInBounce(2*t):.5*o.easeOutBounce(2*t-1)+.5}};s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){ -return window.setTimeout(t,1e3/60)}}(),s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),s.getRelativePosition=function(t,e){var i,a,o=t.originalEvent||t,n=t.currentTarget||t.srcElement,r=n.getBoundingClientRect();o.touches&&o.touches.length>0?(i=o.touches[0].clientX,a=o.touches[0].clientY):(i=o.clientX,a=o.clientY);var l=parseFloat(s.getStyle(n,"padding-left")),h=parseFloat(s.getStyle(n,"padding-top")),c=parseFloat(s.getStyle(n,"padding-right")),u=parseFloat(s.getStyle(n,"padding-bottom")),d=r.right-r.left-l-c,f=r.bottom-r.top-h-u;return i=Math.round((i-r.left-l)/d*n.width/e.currentDevicePixelRatio),a=Math.round((a-r.top-h)/f*n.height/e.currentDevicePixelRatio),{x:i,y:a}},s.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i},s.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=s.noop},s.bindEvents=function(t,e,i){t.events||(t.events={}),s.each(e,function(e){t.events[e]=function(){i.apply(t,arguments)},s.addEvent(t.chart.canvas,e,t.events[e])})},s.unbindEvents=function(t,e){s.each(e,function(e,i){s.removeEvent(t.chart.canvas,i,e)})},s.getConstraintWidth=function(t){return i(t,"max-width","clientWidth")},s.getConstraintHeight=function(t){return i(t,"max-height","clientHeight")},s.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(s.getStyle(e,"padding-left"))+parseInt(s.getStyle(e,"padding-right")),a=e.clientWidth-i,o=s.getConstraintWidth(t);return void 0!==o&&(a=Math.min(a,o)),a},s.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(s.getStyle(e,"padding-top"))+parseInt(s.getStyle(e,"padding-bottom")),a=e.clientHeight-i,o=s.getConstraintHeight(t);return void 0!==o&&(a=Math.min(a,o)),a},s.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},s.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,a=t.canvas.height,s=t.currentDevicePixelRatio=window.devicePixelRatio||1;1!==s&&(e.canvas.height=a*s,e.canvas.width=i*s,e.scale(s,s),t.originalDevicePixelRatio=t.originalDevicePixelRatio||s),e.canvas.style.width=i+"px",e.canvas.style.height=a+"px"},s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},s.fontString=function(t,e,i){return e+" "+t+"px "+i},s.longestText=function(t,e,i,a){a=a||{},a.data=a.data||{},a.garbageCollect=a.garbageCollect||[],a.font!==e&&(a.data={},a.garbageCollect=[],a.font=e),t.font=e;var o=0;s.each(i,function(e){if(void 0!==e&&null!==e){var i=a.data[e];i||(i=a.data[e]=t.measureText(e).width,a.garbageCollect.push(e)),i>o&&(o=i)}});var n=a.garbageCollect.length/2;if(n>i.length){for(var r=0;n>r;r++)delete a.data[a.garbageCollect[r]];a.garbageCollect.splice(0,n)}return o},s.drawRoundedRectangle=function(t,e,i,a,s,o){t.beginPath(),t.moveTo(e+o,i),t.lineTo(e+a-o,i),t.quadraticCurveTo(e+a,i,e+a,i+o),t.lineTo(e+a,i+s-o),t.quadraticCurveTo(e+a,i+s,e+a-o,i+s),t.lineTo(e+o,i+s),t.quadraticCurveTo(e,i+s,e,i+s-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath()},s.color=function(e){return a?a(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.log("Color.js not found!"),e)},s.addResizeListener=function(t,e){var i=document.createElement("iframe"),a="chartjs-hidden-iframe";i.classlist?i.classlist.add(a):i.setAttribute("class",a),i.style.width="100%",i.style.display="block",i.style.border=0,i.style.height=0,i.style.margin=0,i.style.position="absolute",i.style.left=0,i.style.right=0,i.style.top=0,i.style.bottom=0,t.insertBefore(i,t.firstChild),(i.contentWindow||i).onresize=function(){e&&e()}},s.removeResizeListener=function(t){var e=t.querySelector(".chartjs-hidden-iframe");e&&e.parentNode.removeChild(e)},s.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(t)},s.pushAllIfDefined=function(t,e){"undefined"!=typeof t&&(s.isArray(t)?e.push.apply(e,t):e.push(t))},s.isDatasetVisible=function(t){return!t.hidden},s.callCallback=function(t,e,i){t&&"function"==typeof t.call&&t.apply(i,e)}}},{"chartjs-color":5}],26:[function(t,e,i){"use strict";e.exports=function(){var t=function(e,i){this.config=i,e.length&&e[0].getContext&&(e=e[0]),e.getContext&&(e=e.getContext("2d")),this.ctx=e,this.canvas=e.canvas,this.width=e.canvas.width||parseInt(t.helpers.getStyle(e.canvas,"width"))||t.helpers.getMaximumWidth(e.canvas),this.height=e.canvas.height||parseInt(t.helpers.getStyle(e.canvas,"height"))||t.helpers.getMaximumHeight(e.canvas),this.aspectRatio=this.width/this.height,(isNaN(this.aspectRatio)||isFinite(this.aspectRatio)===!1)&&(this.aspectRatio=void 0!==i.aspectRatio?i.aspectRatio:2),this.originalCanvasStyleWidth=e.canvas.style.width,this.originalCanvasStyleHeight=e.canvas.style.height,t.helpers.retinaScale(this),i&&(this.controller=new t.Controller(this));var a=this;return t.helpers.addResizeListener(e.canvas.parentNode,function(){a.controller&&a.controller.config.options.responsive&&a.controller.resize()}),this.controller?this.controller:this};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('
    ');for(var i=0;i'),t.data.datasets[i].label&&e.push(t.data.datasets[i].label),e.push("");return e.push("
"),e.join("")}}},t}},{}],27:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,i,a){function s(t){var e,i=t.isHorizontal();i?(e=t.update(t.options.fullWidth?m:k,y),_-=e.height):(e=t.update(x,v),k-=e.width),D.push({horizontal:i,minSize:e,box:t})}function o(t){var i=e.findNextWhere(D,function(e){return e.box===t});if(i)if(t.isHorizontal()){var a={left:S,right:w,top:0,bottom:0};t.update(t.options.fullWidth?m:k,p/2,a)}else t.update(i.minSize.width,_)}function n(t){var i=e.findNextWhere(D,function(e){return e.box===t}),a={left:0,right:0,top:C,bottom:M};i&&t.update(i.minSize.width,_,a)}function r(t){t.isHorizontal()?(t.left=t.options.fullWidth?l:S,t.right=t.options.fullWidth?i-l:S+k,t.top=F,t.bottom=F+t.height,F=t.bottom):(t.left=T,t.right=T+t.width,t.top=C,t.bottom=C+_,T=t.right)}if(t){var l=0,h=0,c=e.where(t.boxes,function(t){return"left"===t.options.position}),u=e.where(t.boxes,function(t){return"right"===t.options.position}),d=e.where(t.boxes,function(t){return"top"===t.options.position}),f=e.where(t.boxes,function(t){return"bottom"===t.options.position}),g=e.where(t.boxes,function(t){return"chartArea"===t.options.position});d.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),f.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var m=i-2*l,p=a-2*h,b=m/2,v=p/2,x=(i-b)/(c.length+u.length),y=(a-v)/(d.length+f.length),k=m,_=p,D=[];e.each(c.concat(u,d,f),s);var S=l,w=l,C=h,M=h;e.each(c.concat(u),o),e.each(c,function(t){S+=t.width}),e.each(u,function(t){w+=t.width}),e.each(d.concat(f),o),e.each(d,function(t){C+=t.height}),e.each(f,function(t){M+=t.height}),e.each(c.concat(u),n),S=l,w=l,C=h,M=h,e.each(c,function(t){S+=t.width}),e.each(u,function(t){w+=t.width}),e.each(d,function(t){C+=t.height}),e.each(f,function(t){M+=t.height});var A=a-C-M,I=i-S-w;(I!==k||A!==_)&&(e.each(c,function(t){t.height=A}),e.each(u,function(t){t.height=A}),e.each(d,function(t){t.width=I}),e.each(f,function(t){t.width=I}),_=A,k=I);var T=l,F=h;e.each(c.concat(d),r),T+=k,F+=_,e.each(u,r),e.each(f,r),t.chartArea={left:S,top:C,right:S+k,bottom:C+_},e.each(g,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(k,_)})}}}}},{}],28:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var i=this.chart.data.datasets[e.datasetIndex];i.hidden=!i.hidden,this.chart.update()},labels:{boxWidth:40,padding:10,generateLabels:function(t){return e.isArray(t.datasets)?t.datasets.map(function(t,e){return{text:t.label,fillStyle:t.backgroundColor,hidden:t.hidden,lineCap:t.borderCapStyle,lineDash:t.borderDash,lineDashOffset:t.borderDashOffset,lineJoin:t.borderJoinStyle,lineWidth:t.borderWidth,strokeStyle:t.borderColor,datasetIndex:e}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){e.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:e.noop,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:e.noop,beforeSetDimensions:e.noop,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:e.noop,beforeBuildLabels:e.noop,buildLabels:function(){this.legendItems=this.options.labels.generateLabels.call(this,this.chart.data),this.options.reverse&&this.legendItems.reverse()},afterBuildLabels:e.noop,beforeFit:e.noop,fit:function(){var i=this.ctx,a=e.getValueOrDefault(this.options.labels.fontSize,t.defaults.global.defaultFontSize),s=e.getValueOrDefault(this.options.labels.fontStyle,t.defaults.global.defaultFontStyle),o=e.getValueOrDefault(this.options.labels.fontFamily,t.defaults.global.defaultFontFamily),n=e.fontString(a,s,o);if(this.legendHitBoxes=[],this.isHorizontal()?this.minSize.width=this.maxWidth:this.minSize.width=this.options.display?10:0,this.isHorizontal()?this.minSize.height=this.options.display?10:0:this.minSize.height=this.maxHeight,this.options.display&&this.isHorizontal()){this.lineWidths=[0];var r=this.legendItems.length?a+this.options.labels.padding:0;i.textAlign="left",i.textBaseline="top",i.font=n,e.each(this.legendItems,function(t,e){var s=this.options.labels.boxWidth+a/2+i.measureText(t.text).width;this.lineWidths[this.lineWidths.length-1]+s+this.options.labels.padding>=this.width&&(r+=a+this.options.labels.padding,this.lineWidths[this.lineWidths.length]=this.left),this.legendHitBoxes[e]={left:0,top:0,width:s,height:a},this.lineWidths[this.lineWidths.length-1]+=s+this.options.labels.padding},this),this.minSize.height+=r}this.width=this.minSize.width,this.height=this.minSize.height},afterFit:e.noop,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){if(this.options.display){var i=this.ctx,a={x:this.left+(this.width-this.lineWidths[0])/2,y:this.top+this.options.labels.padding,line:0},s=e.getValueOrDefault(this.options.labels.fontColor,t.defaults.global.defaultFontColor),o=e.getValueOrDefault(this.options.labels.fontSize,t.defaults.global.defaultFontSize),n=e.getValueOrDefault(this.options.labels.fontStyle,t.defaults.global.defaultFontStyle),r=e.getValueOrDefault(this.options.labels.fontFamily,t.defaults.global.defaultFontFamily),l=e.fontString(o,n,r);this.isHorizontal()&&(i.textAlign="left",i.textBaseline="top",i.lineWidth=.5,i.strokeStyle=s,i.fillStyle=s,i.font=l,e.each(this.legendItems,function(e,s){var n=i.measureText(e.text).width,r=this.options.labels.boxWidth+o/2+n;a.x+r>=this.width&&(a.y+=o+this.options.labels.padding,a.line++,a.x=this.left+(this.width-this.lineWidths[a.line])/2),i.save();var l=function(t,e){return void 0!==t?t:e};i.fillStyle=l(e.fillStyle,t.defaults.global.defaultColor),i.lineCap=l(e.lineCap,t.defaults.global.elements.line.borderCapStyle),i.lineDashOffset=l(e.lineDashOffset,t.defaults.global.elements.line.borderDashOffset),i.lineJoin=l(e.lineJoin,t.defaults.global.elements.line.borderJoinStyle),i.lineWidth=l(e.lineWidth,t.defaults.global.elements.line.borderWidth),i.strokeStyle=l(e.strokeStyle,t.defaults.global.defaultColor),i.setLineDash&&i.setLineDash(l(e.lineDash,t.defaults.global.elements.line.borderDash)),i.strokeRect(a.x,a.y,this.options.labels.boxWidth,o),i.fillRect(a.x,a.y,this.options.labels.boxWidth,o),i.restore(),this.legendHitBoxes[s].left=a.x,this.legendHitBoxes[s].top=a.y,i.fillText(e.text,this.options.labels.boxWidth+o/2+a.x,a.y),e.hidden&&(i.beginPath(),i.lineWidth=2,i.moveTo(this.options.labels.boxWidth+o/2+a.x,a.y+o/2),i.lineTo(this.options.labels.boxWidth+o/2+a.x+n,a.y+o/2),i.stroke()),a.x+=r+this.options.labels.padding},this))}},handleEvent:function(t){var i=e.getRelativePosition(t,this.chart.chart);if(i.x>=this.left&&i.x<=this.right&&i.y>=this.top&&i.y<=this.bottom)for(var a=0;a=s.left&&i.x<=s.left+s.width&&i.y>=s.top&&i.y<=s.top+s.height){this.options.onClick&&this.options.onClick.call(this,t,this.legendItems[a]);break}}}})}},{}],29:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.scale={display:!0,gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,callback:function(t){return""+t}}},t.Scale=t.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(t,i,a){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=i,this.margins=e.extend({left:0,right:0,top:0,bottom:0},a),this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this.beforeBuildTicks(),this.buildTicks(),this.afterBuildTicks(),this.beforeTickToLabelConversion(),this.convertTicksToLabels(),this.afterTickToLabelConversion(),this.beforeCalculateTickRotation(),this.calculateTickRotation(),this.afterCalculateTickRotation(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop,afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){this.ticks=this.ticks.map(function(t,e,i){return this.options.ticks.userCallback?this.options.ticks.userCallback(t,e,i):this.options.ticks.callback(t,e,i)},this)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),a=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),s=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),o=e.fontString(i,a,s);this.ctx.font=o;var n,r=this.ctx.measureText(this.ticks[0]).width,l=this.ctx.measureText(this.ticks[this.ticks.length-1]).width;if(this.labelRotation=0,this.paddingRight=0,this.paddingLeft=0,this.options.display&&this.isHorizontal()){this.paddingRight=l/2+3,this.paddingLeft=r/2+3,this.longestTextCache||(this.longestTextCache={});for(var h,c,u=e.longestText(this.ctx,o,this.ticks,this.longestTextCache),d=u,f=this.getPixelForTick(1)-this.getPixelForTick(0)-6;d>f&&this.labelRotationthis.yLabelWidth&&(this.paddingLeft=n+i/2),this.paddingRight=i/2,c*u>this.maxHeight){this.labelRotation--;break}this.labelRotation++,d=h*u}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){this.minSize={width:0,height:0};var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),a=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),s=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),o=e.fontString(i,a,s),n=e.getValueOrDefault(this.options.scaleLabel.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(this.options.scaleLabel.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(this.options.scaleLabel.fontFamily,t.defaults.global.defaultFontFamily);e.fontString(n,r,l);if(this.isHorizontal()?this.minSize.width=this.isFullWidth()?this.maxWidth-this.margins.left-this.margins.right:this.maxWidth:this.minSize.width=this.options.gridLines.display&&this.options.display?10:0,this.isHorizontal()?this.minSize.height=this.options.gridLines.display&&this.options.display?10:0:this.minSize.height=this.maxHeight,this.options.scaleLabel.display&&(this.isHorizontal()?this.minSize.height+=1.5*n:this.minSize.width+=1.5*n),this.options.ticks.display&&this.options.display){this.longestTextCache||(this.longestTextCache={});var h=e.longestText(this.ctx,o,this.ticks,this.longestTextCache);if(this.isHorizontal()){this.longestLabelWidth=h;var c=Math.sin(e.toRadians(this.labelRotation))*this.longestLabelWidth+1.5*i;this.minSize.height=Math.min(this.maxHeight,this.minSize.height+c),this.ctx.font=o;var u=this.ctx.measureText(this.ticks[0]).width,d=this.ctx.measureText(this.ticks[this.ticks.length-1]).width,f=Math.cos(e.toRadians(this.labelRotation)),g=Math.sin(e.toRadians(this.labelRotation));this.paddingLeft=0!==this.labelRotation?f*u+3:u/2+3,this.paddingRight=0!==this.labelRotation?g*(i/2)+3:d/2+3}else{var m=this.maxWidth-this.minSize.width;this.options.ticks.mirror||(h+=this.options.ticks.padding),m>h?this.minSize.width+=h:this.minSize.width=this.maxWidth,this.paddingTop=i/2,this.paddingBottom=i/2}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingTop=Math.max(this.paddingTop-this.margins.top,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0),this.paddingBottom=Math.max(this.paddingBottom-this.margins.bottom,0)),this.width=this.minSize.width,this.height=this.minSize.height},afterFit:function(){e.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function i(t){return null===t||"undefined"==typeof t?NaN:"number"==typeof t&&isNaN(t)?NaN:"object"==typeof t?t instanceof Date?t:i(this.isHorizontal()?t.x:t.y):t},getLabelForIndex:e.noop,getPixelForValue:e.noop,getPixelForTick:function(t,e){if(this.isHorizontal()){var i=this.width-(this.paddingLeft+this.paddingRight),a=i/Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1),s=a*t+this.paddingLeft;e&&(s+=a/2);var o=this.left+Math.round(s);return o+=this.isFullWidth()?this.margins.left:0}var n=this.height-(this.paddingTop+this.paddingBottom);return this.top+t*(n/(this.ticks.length-1))},getPixelForDecimal:function(t){if(this.isHorizontal()){var e=this.width-(this.paddingLeft+this.paddingRight),i=e*t+this.paddingLeft,a=this.left+Math.round(i);return a+=this.isFullWidth()?this.margins.left:0}return this.top+t*this.height},draw:function(i){if(this.options.display){var a,s,o,n,r,l=0!==this.labelRotation,h=this.options.ticks.autoSkip;this.options.ticks.maxTicksLimit&&(r=this.options.ticks.maxTicksLimit);var c=e.getValueOrDefault(this.options.ticks.fontColor,t.defaults.global.defaultFontColor),u=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),d=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),f=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),g=e.fontString(u,d,f),m=e.getValueOrDefault(this.options.scaleLabel.fontColor,t.defaults.global.defaultFontColor),p=e.getValueOrDefault(this.options.scaleLabel.fontSize,t.defaults.global.defaultFontSize),b=e.getValueOrDefault(this.options.scaleLabel.fontStyle,t.defaults.global.defaultFontStyle),v=e.getValueOrDefault(this.options.scaleLabel.fontFamily,t.defaults.global.defaultFontFamily),x=e.fontString(p,b,v),y=Math.cos(e.toRadians(this.labelRotation)),k=(Math.sin(e.toRadians(this.labelRotation)),this.longestLabelWidth*y);if(this.ctx.fillStyle=c,this.isHorizontal()){a=!0;var _="bottom"===this.options.position?this.top:this.bottom-10,D="bottom"===this.options.position?this.top+10:this.bottom;if(s=!1,(k/2+this.options.ticks.autoSkipPadding)*this.ticks.length>this.width-(this.paddingLeft+this.paddingRight)&&(s=1+Math.floor((k/2+this.options.ticks.autoSkipPadding)*this.ticks.length/(this.width-(this.paddingLeft+this.paddingRight)))),r&&this.ticks.length>r)for(;!s||this.ticks.length/(s||1)>r;)s||(s=1),s+=1;h||(s=!1),e.each(this.ticks,function(t,o){var n=this.ticks.length===o+1,r=s>1&&o%s>0||o%s===0&&o+s>this.ticks.length;if((!r||n)&&void 0!==t&&null!==t){var h=this.getPixelForTick(o),c=this.getPixelForTick(o,this.options.gridLines.offsetGridLines);this.options.gridLines.display&&(o===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,a=!0):a&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,a=!1),h+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,_),this.ctx.lineTo(h,D)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,i.top),this.ctx.lineTo(h,i.bottom)),this.ctx.stroke()),this.options.ticks.display&&(this.ctx.save(),this.ctx.translate(c,l?this.top+12:"top"===this.options.position?this.bottom-10:this.top+10),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textAlign=l?"right":"center",this.ctx.textBaseline=l?"middle":"top"===this.options.position?"bottom":"top",this.ctx.fillText(t,0,0),this.ctx.restore())}},this),this.options.scaleLabel.display&&(this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillStyle=m,this.ctx.font=x,o=this.left+(this.right-this.left)/2,n="bottom"===this.options.position?this.bottom-p/2:this.top+p/2,this.ctx.fillText(this.options.scaleLabel.labelString,o,n))}else{a=!0;var S="right"===this.options.position?this.left:this.right-5,w="right"===this.options.position?this.left+5:this.right;if(e.each(this.ticks,function(t,s){if(void 0!==t&&null!==t){var o=this.getPixelForTick(s);if(this.options.gridLines.display&&(s===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,a=!0):a&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,a=!1),o+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(S,o),this.ctx.lineTo(w,o)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(i.left,o),this.ctx.lineTo(i.right,o)),this.ctx.stroke()),this.options.ticks.display){var n,r=this.getPixelForTick(s,this.options.gridLines.offsetGridLines);this.ctx.save(),"left"===this.options.position?this.options.ticks.mirror?(n=this.right+this.options.ticks.padding,this.ctx.textAlign="left"):(n=this.right-this.options.ticks.padding,this.ctx.textAlign="right"):this.options.ticks.mirror?(n=this.left-this.options.ticks.padding,this.ctx.textAlign="right"):(n=this.left+this.options.ticks.padding,this.ctx.textAlign="left"),this.ctx.translate(n,r),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textBaseline="middle",this.ctx.fillText(t,0,0),this.ctx.restore()}}},this),this.options.scaleLabel.display){o="left"===this.options.position?this.left+p/2:this.right-p/2,n=this.top+(this.bottom-this.top)/2;var C="left"===this.options.position?-.5*Math.PI:.5*Math.PI;this.ctx.save(),this.ctx.translate(o,n),this.ctx.rotate(C),this.ctx.textAlign="center",this.ctx.fillStyle=m,this.ctx.font=x,this.ctx.textBaseline="middle",this.ctx.fillText(this.options.scaleLabel.labelString,0,0),this.ctx.restore()}}this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color;var M=this.left,A=this.right,I=this.top,T=this.bottom;this.isHorizontal()?(I=T="top"===this.options.position?this.bottom:this.top,I+=e.aliasPixel(this.ctx.lineWidth),T+=e.aliasPixel(this.ctx.lineWidth)):(M=A="left"===this.options.position?this.right:this.left,M+=e.aliasPixel(this.ctx.lineWidth),A+=e.aliasPixel(this.ctx.lineWidth)),this.ctx.beginPath(),this.ctx.moveTo(M,I),this.ctx.lineTo(A,T),this.ctx.stroke()}}})}},{}],30:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.scaleService={constructors:{},defaults:{},registerScaleType:function(t,i,a){this.constructors[t]=i,this.defaults[t]=e.clone(a)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(i){return this.defaults.hasOwnProperty(i)?e.scaleMerge(t.defaults.scale,this.defaults[i]):{}},addScalesToLayout:function(i){e.each(i.scales,function(e){t.layoutService.addBox(i,e)})}}}},{}],31:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10,text:""},t.Title=t.Element.extend({initialize:function(i){e.extend(this,i),this.options=e.configMerge(t.defaults.global.title,i.options),this.legendHitBoxes=[]},beforeUpdate:e.noop,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:e.noop,beforeSetDimensions:e.noop,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:e.noop,beforeBuildLabels:e.noop,buildLabels:e.noop,afterBuildLabels:e.noop,beforeFit:e.noop,fit:function(){var i=(this.ctx,e.getValueOrDefault(this.options.fontSize,t.defaults.global.defaultFontSize)),a=e.getValueOrDefault(this.options.fontStyle,t.defaults.global.defaultFontStyle),s=e.getValueOrDefault(this.options.fontFamily,t.defaults.global.defaultFontFamily);e.fontString(i,a,s);this.isHorizontal()?this.minSize.width=this.maxWidth:this.minSize.width=0,this.isHorizontal()?this.minSize.height=0:this.minSize.height=this.maxHeight,this.isHorizontal()?this.options.display&&(this.minSize.height+=i+2*this.options.padding):this.options.display&&(this.minSize.width+=i+2*this.options.padding),this.width=this.minSize.width,this.height=this.minSize.height},afterFit:e.noop,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){if(this.options.display){var i,a,s=this.ctx,o=e.getValueOrDefault(this.options.fontColor,t.defaults.global.defaultFontColor),n=e.getValueOrDefault(this.options.fontSize,t.defaults.global.defaultFontSize),r=e.getValueOrDefault(this.options.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(this.options.fontFamily,t.defaults.global.defaultFontFamily),h=e.fontString(n,r,l);if(s.fillStyle=o,s.font=h,this.isHorizontal())s.textAlign="center",s.textBaseline="middle",i=this.left+(this.right-this.left)/2,a=this.top+(this.bottom-this.top)/2,s.fillText(this.options.text,i,a);else{i="left"===this.options.position?this.left+n/2:this.right-n/2,a=this.top+(this.bottom-this.top)/2;var c="left"===this.options.position?-.5*Math.PI:.5*Math.PI;s.save(),s.translate(i,a),s.rotate(c),s.textAlign="center",s.textBaseline="middle",s.fillText(this.options.text,0,0),s.restore()}}}})}},{}],32:[function(t,e,i){"use strict";e.exports=function(t){function e(t,e){return e&&(i.isArray(e)?t=t.concat(e):t.push(e)),t}var i=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"single",backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleColor:"#fff",titleAlign:"left",bodySpacing:2,bodyColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,yAlign:"center",xAlign:"center",caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",callbacks:{beforeTitle:i.noop,title:function(t,e){var i="";return t.length>0&&(t[0].xLabel?i=t[0].xLabel:e.labels.length>0&&t[0].indexthis._chart.height-t.height&&(this._model.yAlign="bottom");var e,i,a,s,o,n=this,r=(this._chartInstance.chartArea.left+this._chartInstance.chartArea.right)/2,l=(this._chartInstance.chartArea.top+this._chartInstance.chartArea.bottom)/2;"center"===this._model.yAlign?(e=function(t){return r>=t},i=function(t){return t>r}):(e=function(e){return e<=t.width/2},i=function(e){return e>=n._chart.width-t.width/2}),a=function(e){return e+t.width>n._chart.width},s=function(e){return e-t.width<0},o=function(t){return l>=t?"top":"bottom"},e(this._model.x)?(this._model.xAlign="left",a(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y))):i(this._model.x)&&(this._model.xAlign="right",s(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y)))},getBackgroundPoint:function(t,e){var i={x:t.x,y:t.y};return"right"===t.xAlign?i.x-=e.width:"center"===t.xAlign&&(i.x-=e.width/2),"top"===t.yAlign?i.y+=t.caretPadding+t.caretSize:"bottom"===t.yAlign?i.y-=e.height+t.caretPadding+t.caretSize:i.y-=e.height/2,"center"===t.yAlign?"left"===t.xAlign?i.x+=t.caretPadding+t.caretSize:"right"===t.xAlign&&(i.x-=t.caretPadding+t.caretSize):"left"===t.xAlign?i.x-=t.cornerRadius+t.caretPadding:"right"===t.xAlign&&(i.x+=t.cornerRadius+t.caretPadding),i},drawCaret:function(t,e,a,s){var o,n,r,l,h,c,u=this._view,d=this._chart.ctx;"center"===u.yAlign?("left"===u.xAlign?(o=t.x,n=o-u.caretSize,r=o):(o=t.x+e.width,n=o+u.caretSize,r=o),h=t.y+e.height/2,l=h-u.caretSize,c=h+u.caretSize):("left"===u.xAlign?(o=t.x+u.cornerRadius,n=o+u.caretSize,r=n+u.caretSize):"right"===u.xAlign?(o=t.x+e.width-u.cornerRadius,n=o-u.caretSize,r=n-u.caretSize):(n=t.x+e.width/2,o=n-u.caretSize,r=n+u.caretSize),"top"===u.yAlign?(l=t.y,h=l-u.caretSize,c=l):(l=t.y+e.height,h=l+u.caretSize,c=l));var f=i.color(u.backgroundColor);d.fillStyle=f.alpha(a*f.alpha()).rgbString(),d.beginPath(),d.moveTo(o,l),d.lineTo(n,h),d.lineTo(r,c),d.closePath(),d.fill()},drawTitle:function(t,e,a,s){if(e.title.length){a.textAlign=e._titleAlign,a.textBaseline="top";var o=i.color(e.titleColor);a.fillStyle=o.alpha(s*o.alpha()).rgbString(),a.font=i.fontString(e.titleFontSize,e._titleFontStyle,e._titleFontFamily),i.each(e.title,function(i,s){a.fillText(i,t.x,t.y),t.y+=e.titleFontSize+e.titleSpacing,s+1===e.title.length&&(t.y+=e.titleMarginBottom-e.titleSpacing)})}},drawBody:function(t,e,a,s){a.textAlign=e._bodyAlign,a.textBaseline="top";var o=i.color(e.bodyColor);a.fillStyle=o.alpha(s*o.alpha()).rgbString(),a.font=i.fontString(e.bodyFontSize,e._bodyFontStyle,e._bodyFontFamily),i.each(e.beforeBody,function(i){a.fillText(i,t.x,t.y),t.y+=e.bodyFontSize+e.bodySpacing}),i.each(e.body,function(o,n){"single"!==this._options.tooltips.mode&&(a.fillStyle=i.color(e.legendColorBackground).alpha(s).rgbaString(),a.fillRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),a.strokeStyle=i.color(e.labelColors[n].borderColor).alpha(s).rgbaString(),a.strokeRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),a.fillStyle=i.color(e.labelColors[n].backgroundColor).alpha(s).rgbaString(),a.fillRect(t.x+1,t.y+1,e.bodyFontSize-2,e.bodyFontSize-2),a.fillStyle=i.color(e.bodyColor).alpha(s).rgbaString()),a.fillText(o,t.x+("single"!==this._options.tooltips.mode?e.bodyFontSize+2:0),t.y),t.y+=e.bodyFontSize+e.bodySpacing},this),i.each(e.afterBody,function(i){a.fillText(i,t.x,t.y),t.y+=e.bodyFontSize}),t.y-=e.bodySpacing},drawFooter:function(t,e,a,s){if(e.footer.length){t.y+=e.footerMarginTop,a.textAlign=e._footerAlign,a.textBaseline="top";var o=i.color(e.footerColor);a.fillStyle=o.alpha(s*o.alpha()).rgbString(),a.font=i.fontString(e.footerFontSize,e._footerFontStyle,e._footerFontFamily),i.each(e.footer,function(i){a.fillText(i,t.x,t.y),t.y+=e.footerFontSize+e.footerSpacing})}},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var a=e.caretPadding,s=this.getTooltipSize(e),o={x:e.x,y:e.y},n=Math.abs(e.opacity<.001)?0:e.opacity;if(this._options.tooltips.enabled){var r=i.color(e.backgroundColor);t.fillStyle=r.alpha(n*r.alpha()).rgbString(),i.drawRoundedRectangle(t,o.x,o.y,s.width,s.height,e.cornerRadius),t.fill(),this.drawCaret(o,s,n,a),o.x+=e.xPadding,o.y+=e.yPadding,this.drawTitle(o,e,t,n),this.drawBody(o,e,t,n),this.drawFooter(o,e,t,n)}}}})}},{}],33:[function(t,e,i){"use strict";e.exports=function(t,e){var i=t.helpers;t.defaults.global.elements.arc={backgroundColor:t.defaults.global.defaultColor,borderColor:"#fff",borderWidth:2},t.elements.Arc=t.Element.extend({inLabelRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)n;)n+=2*Math.PI;for(;s.angle>n;)s.angle-=2*Math.PI;for(;s.angle=o&&s.angle<=n,l=s.distance>=a.innerRadius&&s.distance<=a.outerRadius;return r&&l}return!1},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,e.startAngle,e.endAngle),t.arc(e.x,e.y,e.innerRadius,e.endAngle,e.startAngle,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],34:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.elements.line={tension:.4,backgroundColor:t.defaults.global.defaultColor,borderWidth:3,borderColor:t.defaults.global.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",fill:!0},t.elements.Line=t.Element.extend({lineToNextPoint:function(t,e,i,a,s){var o=this._chart.ctx;e._view.skip?a.call(this,t,e,i):t._view.skip?s.call(this,t,e,i):0===e._view.tension?o.lineTo(e._view.x,e._view.y):o.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y)},draw:function(){function i(t){n._view.skip||r._view.skip?t&&o.lineTo(a._view.scaleZero.x,a._view.scaleZero.y):o.bezierCurveTo(r._view.controlPointNextX,r._view.controlPointNextY,n._view.controlPointPreviousX,n._view.controlPointPreviousY,n._view.x,n._view.y)}var a=this,s=this._view,o=this._chart.ctx,n=this._children[0],r=this._children[this._children.length-1];o.save(),this._children.length>0&&s.fill&&(o.beginPath(),e.each(this._children,function(t,i){var a=e.previousItem(this._children,i),n=e.nextItem(this._children,i);0===i?(this._loop?o.moveTo(s.scaleZero.x,s.scaleZero.y):o.moveTo(t._view.x,s.scaleZero),t._view.skip?this._loop||o.moveTo(n._view.x,this._view.scaleZero):o.lineTo(t._view.x,t._view.y)):this.lineToNextPoint(a,t,n,function(t,e,i){this._loop?o.lineTo(this._view.scaleZero.x,this._view.scaleZero.y):(o.lineTo(t._view.x,this._view.scaleZero),o.moveTo(i._view.x,this._view.scaleZero))},function(t,e){o.lineTo(e._view.x,e._view.y)})},this),this._loop?i(!0):(o.lineTo(this._children[this._children.length-1]._view.x,s.scaleZero),o.lineTo(this._children[0]._view.x,s.scaleZero)),o.fillStyle=s.backgroundColor||t.defaults.global.defaultColor,o.closePath(),o.fill()),o.lineCap=s.borderCapStyle||t.defaults.global.elements.line.borderCapStyle,o.setLineDash&&o.setLineDash(s.borderDash||t.defaults.global.elements.line.borderDash),o.lineDashOffset=s.borderDashOffset||t.defaults.global.elements.line.borderDashOffset,o.lineJoin=s.borderJoinStyle||t.defaults.global.elements.line.borderJoinStyle,o.lineWidth=s.borderWidth||t.defaults.global.elements.line.borderWidth,o.strokeStyle=s.borderColor||t.defaults.global.defaultColor,o.beginPath(),e.each(this._children,function(t,i){var a=e.previousItem(this._children,i),s=e.nextItem(this._children,i);0===i?o.moveTo(t._view.x,t._view.y):this.lineToNextPoint(a,t,s,function(t,e,i){o.moveTo(i._view.x,i._view.y)},function(t,e){o.moveTo(e._view.x,e._view.y)})},this),this._loop&&this._children.length>0&&i(),o.stroke(),o.restore()}})}},{}],35:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.elements.point={radius:3,pointStyle:"circle",backgroundColor:t.defaults.global.defaultColor,borderWidth:1,borderColor:t.defaults.global.defaultColor,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},t.elements.Point=t.Element.extend({inRange:function(t,e){var i=this._view;if(i){var a=i.hitRadius+i.radius;return Math.pow(t-i.x,2)+Math.pow(e-i.y,2)0){a.strokeStyle=i.borderColor||t.defaults.global.defaultColor,a.lineWidth=e.getValueOrDefault(i.borderWidth,t.defaults.global.elements.point.borderWidth),a.fillStyle=i.backgroundColor||t.defaults.global.defaultColor;var s,o,n=i.radius;switch(i.pointStyle){default:a.beginPath(),a.arc(i.x,i.y,n,0,2*Math.PI),a.closePath(),a.fill();break;case"triangle":a.beginPath();var r=3*n/Math.sqrt(3),l=r*Math.sqrt(3)/2;a.moveTo(i.x-r/2,i.y+l/3),a.lineTo(i.x+r/2,i.y+l/3),a.lineTo(i.x,i.y-2*l/3),a.closePath(),a.fill();break;case"rect":a.fillRect(i.x-1/Math.SQRT2*n,i.y-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n),a.strokeRect(i.x-1/Math.SQRT2*n,i.y-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n);break;case"rectRot":a.translate(i.x,i.y),a.rotate(Math.PI/4),a.fillRect(-1/Math.SQRT2*n,-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n),a.strokeRect(-1/Math.SQRT2*n,-1/Math.SQRT2*n,2/Math.SQRT2*n,2/Math.SQRT2*n),a.setTransform(1,0,0,1,0,0);break;case"cross":a.beginPath(),a.moveTo(i.x,i.y+n),a.lineTo(i.x,i.y-n),a.moveTo(i.x-n,i.y),a.lineTo(i.x+n,i.y),a.closePath();break;case"crossRot":a.beginPath(),s=Math.cos(Math.PI/4)*n,o=Math.sin(Math.PI/4)*n,a.moveTo(i.x-s,i.y-o),a.lineTo(i.x+s,i.y+o),a.moveTo(i.x-s,i.y+o),a.lineTo(i.x+s,i.y-o),a.closePath();break;case"star":a.beginPath(),a.moveTo(i.x,i.y+n),a.lineTo(i.x,i.y-n),a.moveTo(i.x-n,i.y),a.lineTo(i.x+n,i.y),s=Math.cos(Math.PI/4)*n,o=Math.sin(Math.PI/4)*n,a.moveTo(i.x-s,i.y-o),a.lineTo(i.x+s,i.y+o),a.moveTo(i.x-s,i.y+o),a.lineTo(i.x+s,i.y-o),a.closePath();break;case"line":a.beginPath(),a.moveTo(i.x-n,i.y),a.lineTo(i.x+n,i.y),a.closePath();break;case"dash":a.beginPath(),a.moveTo(i.x,i.y),a.lineTo(i.x+n,i.y),a.closePath()}a.stroke()}}}})}},{}],36:[function(t,e,i){"use strict";e.exports=function(t){t.helpers;t.defaults.global.elements.rectangle={backgroundColor:t.defaults.global.defaultColor,borderWidth:0,borderColor:t.defaults.global.defaultColor,borderSkipped:"bottom"},t.elements.Rectangle=t.Element.extend({draw:function(){function t(t){return l[(c+t)%4]}var e=this._chart.ctx,i=this._view,a=i.width/2,s=i.x-a,o=i.x+a,n=i.base-(i.base-i.y),r=i.borderWidth/2;i.borderWidth&&(s+=r,o-=r,n+=r),e.beginPath(),e.fillStyle=i.backgroundColor,e.strokeStyle=i.borderColor,e.lineWidth=i.borderWidth;var l=[[s,i.base],[s,n],[o,n],[o,i.base]],h=["bottom","left","top","right"],c=h.indexOf(i.borderSkipped,0);-1===c&&(c=0),e.moveTo.apply(e,t(0));for(var u=1;4>u;u++)e.lineTo.apply(e,t(u));e.fill(),i.borderWidth&&e.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view,a=!1;return i&&(a=i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y),a},inLabelRange:function(t){var e=this._view;return e?t>=e.x-e.width/2&&t<=e.x+e.width/2:!1},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],37:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"bottom"},a=t.Scale.extend({buildTicks:function(t){this.startIndex=0,this.endIndex=this.chart.data.labels.length;var i;void 0!==this.options.ticks.min&&(i=e.indexOf(this.chart.data.labels,this.options.ticks.min),this.startIndex=-1!==i?i:this.startIndex),void 0!==this.options.ticks.max&&(i=e.indexOf(this.chart.data.labels,this.options.ticks.max),this.endIndex=-1!==i?i:this.endIndex),this.ticks=0===this.startIndex&&this.endIndex===this.chart.data.labels.length?this.chart.data.labels:this.chart.data.labels.slice(this.startIndex,this.endIndex+1)},getLabelForIndex:function(t,e){return this.ticks[t]},getPixelForValue:function(t,e,i,a){var s=Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1);if(this.isHorizontal()){var o=this.width-(this.paddingLeft+this.paddingRight),n=o/s,r=n*(e-this.startIndex)+this.paddingLeft;return this.options.gridLines.offsetGridLines&&a&&(r+=n/2),this.left+Math.round(r)}var l=this.height-(this.paddingTop+this.paddingBottom),h=l/s,c=h*(e-this.startIndex)+this.paddingTop;return this.options.gridLines.offsetGridLines&&a&&(c+=h/2),this.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.startIndex,null,e)}});t.scaleService.registerScaleType("category",a,i)}},{}],38:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(t,i,a){var s=a[1]-a[0];Math.abs(s)>1&&t!==Math.floor(t)&&(s=t-Math.floor(t));var o=e.log10(Math.abs(s)),n="";if(0!==t){var r=-1*Math.floor(o);r=Math.max(Math.min(r,20),0),n=t.toFixed(r)}else n="0";return n}}},a=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={},i=!1,a=!1;e.each(this.chart.data.datasets,function(s){void 0===t[s.type]&&(t[s.type]={positiveValues:[],negativeValues:[]});var o=t[s.type].positiveValues,n=t[s.type].negativeValues;e.isDatasetVisible(s)&&(this.isHorizontal()?s.xAxisID===this.id:s.yAxisID===this.id)&&e.each(s.data,function(t,e){var s=+this.getRightValue(t);isNaN(s)||(o[e]=o[e]||0,n[e]=n[e]||0,this.options.relativePoints?o[e]=100:0>s?(a=!0,n[e]+=s):(i=!0,o[e]+=s))},this)},this),e.each(t,function(t){var i=t.positiveValues.concat(t.negativeValues),a=e.min(i),s=e.max(i);this.min=null===this.min?a:Math.min(this.min,a),this.max=null===this.max?s:Math.max(this.max,s)},this)}else e.each(this.chart.data.datasets,function(t){e.isDatasetVisible(t)&&(this.isHorizontal()?t.xAxisID===this.id:t.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);if(this.options.ticks.beginAtZero){var s=e.sign(this.min),o=e.sign(this.max);0>s&&0>o?this.max=0:s>0&&o>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.min--,this.max++)},buildTicks:function(){this.ticks=[];var i;if(this.isHorizontal())i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.width/50));else{var a=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize);i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.height/(2*a)))}i=Math.max(2,i);var s,o=this.options.ticks.fixedStepSize&&this.options.ticks.fixedStepSize>0||this.options.ticks.stepSize&&this.options.ticks.stepSize>0;if(o)s=e.getValueOrDefault(this.options.ticks.fixedStepSize,this.options.ticks.stepSize);else{var n=e.niceNum(this.max-this.min,!1);s=e.niceNum(n/(i-1),!0)}var r=Math.floor(this.min/s)*s,l=Math.ceil(this.max/s)*s,h=(l-r)/s;h=e.almostEquals(h,Math.round(h),s/1e3)?Math.round(h):Math.ceil(h),this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:r);for(var c=1;h>c;++c)this.ticks.push(r+c*s);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:l),("left"===this.options.position||"right"===this.options.position)&&this.ticks.reverse(),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},convertTicksToLabels:function(){this.ticksAsNumbers=this.ticks.slice(),this.zeroLineIndex=this.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(this)},getPixelForValue:function(t,e,i,a){var s,o=+this.getRightValue(t),n=this.end-this.start;if(this.isHorizontal()){var r=this.width-(this.paddingLeft+this.paddingRight);return s=this.left+r/n*(o-this.start),Math.round(s+this.paddingLeft)}var l=this.height-(this.paddingTop+this.paddingBottom);return s=this.bottom-this.paddingBottom-l/n*(o-this.start),Math.round(s)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticksAsNumbers[t],null,null,e)}});t.scaleService.registerScaleType("linear",a,i)}},{}],39:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(e,i,a){var s=e/Math.pow(10,Math.floor(t.helpers.log10(e)));return 1===s||2===s||5===s||0===i||i===a.length-1?e.toExponential():""}}},a=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={};e.each(this.chart.data.datasets,function(i){e.isDatasetVisible(i)&&(this.isHorizontal()?i.xAxisID===this.id:i.yAxisID===this.id)&&(void 0===t[i.type]&&(t[i.type]=[]),e.each(i.data,function(e,a){var s=t[i.type],o=+this.getRightValue(e);isNaN(o)||(s[a]=s[a]||0,this.options.relativePoints?s[a]=100:s[a]+=o)},this))},this),e.each(t,function(t){var i=e.min(t),a=e.max(t);this.min=null===this.min?i:Math.min(this.min,i),this.max=null===this.max?a:Math.max(this.max,a)},this)}else e.each(this.chart.data.datasets,function(t){e.isDatasetVisible(t)&&(this.isHorizontal()?t.xAxisID===this.id:t.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);this.min=void 0!==this.options.ticks.min?this.options.ticks.min:this.min,this.max=void 0!==this.options.ticks.max?this.options.ticks.max:this.max,this.min===this.max&&(0!==this.min&&null!==this.min?(this.min=Math.pow(10,Math.floor(e.log10(this.min))-1),this.max=Math.pow(10,Math.floor(e.log10(this.max))+1)):(this.min=1,this.max=10))},buildTicks:function(){this.ticks=[];for(var t=void 0!==this.options.ticks.min?this.options.ticks.min:Math.pow(10,Math.floor(e.log10(this.min)));tthis.max&&(this.max=i))},this)},this),this.options.ticks.beginAtZero){var t=e.sign(this.min),i=e.sign(this.max);0>t&&0>i?this.max=0:t>0&&i>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.min--,this.max++)},buildTicks:function(){this.ticks=[];var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),a=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.drawingArea/(1.5*i)));a=Math.max(2,a);var s=e.niceNum(this.max-this.min,!1),o=e.niceNum(s/(a-1),!0),n=Math.floor(this.min/o)*o,r=Math.ceil(this.max/o)*o,l=Math.ceil((r-n)/o);this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:n);for(var h=1;l>h;++h)this.ticks.push(n+h*o);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:r),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),this.zeroLineIndex=this.ticks.indexOf(0)},convertTicksToLabels:function(){t.Scale.prototype.convertTicksToLabels.call(this),this.pointLabels=this.chart.data.labels.map(this.options.pointLabels.callback,this)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var i,a,s,o,n,r,l,h,c,u,d,f,g=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),m=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),p=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),b=e.fontString(g,m,p),v=e.min([this.height/2-g-5,this.width/2]),x=this.width,y=0;for(this.ctx.font=b,a=0;ax&&(x=i.x+o,n=a),i.x-ox&&(x=i.x+s,n=a):a>this.getValueCount()/2&&i.x-s0||this.options.reverse){var o=this.getDistanceFromCenterForValue(this.ticks[s]),n=this.yCenter-o;if(this.options.gridLines.display)if(i.strokeStyle=this.options.gridLines.color,i.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)i.beginPath(),i.arc(this.xCenter,this.yCenter,o,0,2*Math.PI),i.closePath(),i.stroke();else{i.beginPath();for(var r=0;r=0;a--){if(this.options.angleLines.display){var s=this.getPointPosition(a,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max));i.beginPath(),i.moveTo(this.xCenter,this.yCenter),i.lineTo(s.x,s.y),i.stroke(),i.closePath()}var o=this.getPointPosition(a,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max)+5),n=e.getValueOrDefault(this.options.pointLabels.fontColor,t.defaults.global.defaultFontColor),r=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),l=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),h=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),c=e.fontString(r,l,h);i.font=c,i.fillStyle=n;var u=this.pointLabels.length,d=this.pointLabels.length/2,f=d/2,g=f>a||a>u-f,m=a===f||a===u-f;0===a?i.textAlign="center":a===d?i.textAlign="center":d>a?i.textAlign="left":i.textAlign="right",m?i.textBaseline="middle":g?i.textBaseline="bottom":i.textBaseline="top",i.fillText(this.pointLabels[a]?this.pointLabels[a]:"",o.x,o.y)}}}}});t.scaleService.registerScaleType("radialLinear",a,i)}},{}],41:[function(t,e,i){"use strict";var a=t("moment");a="function"==typeof a?a:window.moment,e.exports=function(t){var e=t.helpers,i={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12] -},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},s={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},o=t.Scale.extend({initialize:function(){if(!a)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return this.labelMoments[t][e]},determineDataLimits:function(){this.labelMoments=[];var t=[];this.chart.data.labels&&this.chart.data.labels.length>0?(e.each(this.chart.data.labels,function(e,i){var a=this.parseTime(e);this.options.time.round&&a.startOf(this.options.time.round),t.push(a)},this),this.firstTick=a.min.call(this,t),this.lastTick=a.max.call(this,t)):(this.firstTick=null,this.lastTick=null),e.each(this.chart.data.datasets,function(i,s){var o=[];"object"==typeof i.data[0]?e.each(i.data,function(t,e){var i=this.parseTime(this.getRightValue(t));this.options.time.round&&i.startOf(this.options.time.round),o.push(i),this.firstTick=null!==this.firstTick?a.min(this.firstTick,i):i,this.lastTick=null!==this.lastTick?a.max(this.lastTick,i):i},this):o=t,this.labelMoments.push(o)},this),this.options.time.min&&(this.firstTick=this.parseTime(this.options.time.min)),this.options.time.max&&(this.lastTick=this.parseTime(this.options.time.max)),this.firstTick=(this.firstTick||a()).clone(),this.lastTick=(this.lastTick||a()).clone()},buildTicks:function(a){this.ctx.save();var s=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),o=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),n=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),r=e.fontString(s,o,n);if(this.ctx.font=r,this.ticks=[],this.unitScale=1,this.scaleSizeInUnits=0,this.options.time.unit)this.tickUnit=this.options.time.unit||"day",this.displayFormat=this.options.time.displayFormats[this.tickUnit],this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,1);else{var l=this.isHorizontal()?this.width-(this.paddingLeft+this.paddingRight):this.height-(this.paddingTop+this.paddingBottom),h=this.tickFormatFunction(this.firstTick,0,[]),c=this.ctx.measureText(h).width,u=Math.cos(e.toRadians(this.options.ticks.maxRotation)),d=Math.sin(e.toRadians(this.options.ticks.maxRotation));c=c*u+s*d;var f=l/c;this.tickUnit="millisecond",this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.displayFormat=this.options.time.displayFormats[this.tickUnit];for(var g=0,m=i.units[g];g=Math.ceil(this.scaleSizeInUnits/f)){this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(this.scaleSizeInUnits/f)=0)break;v%this.unitScale===0&&this.ticks.push(x)}(0!==this.ticks[this.ticks.length-1].diff(this.lastTick,this.tickUnit)||0===this.scaleSizeInUnits)&&(this.options.time.max?(this.ticks.push(this.lastTick.clone()),this.scaleSizeInUnits=this.lastTick.diff(this.ticks[0],this.tickUnit,!0)):(this.scaleSizeInUnits=Math.ceil(this.scaleSizeInUnits/this.unitScale)*this.unitScale,this.ticks.push(this.firstTick.clone().add(this.scaleSizeInUnits,this.tickUnit)),this.lastTick=this.ticks[this.ticks.length-1].clone())),this.ctx.restore()},getLabelForIndex:function(t,e){var i=this.chart.data.labels&&te||t[3]&&t[3]<1?d(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function d(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function u(t,e){if(1>e||t[3]&&t[3]<1)return f(t,e);var i=Math.round(t[0]/255*100),s=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+i+"%, "+s+"%, "+a+"%)"}function f(t,e){var i=Math.round(t[0]/255*100),s=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgba("+i+"%, "+s+"%, "+a+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?m(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function b(t){return k[t.slice(0,3)]}function v(t,e,i){return Math.min(Math.max(e,t),i)}function x(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y=t("color-name");e.exports={getRgba:s,getHsla:a,getRgb:n,getHsl:r,getHwb:o,getAlpha:h,hexString:l,rgbString:c,rgbaString:d,percentString:u,percentaString:f,hslString:g,hslaString:m,hwbString:p,keyword:b};var k={};for(var _ in y)k[y[_]]=_},{"color-name":5}],2:[function(t,e,i){var s=t("color-convert"),a=t("chartjs-color-string"),o=function(t){if(t instanceof o)return t;if(!(this instanceof o))return new o(t);if(this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},"string"==typeof t){var e=a.getRgba(t);if(e)this.setValues("rgb",e);else if(e=a.getHsla(t))this.setValues("hsl",e);else{if(!(e=a.getHwb(t)))throw new Error('Unable to parse color from string "'+t+'"');this.setValues("hwb",e)}}else if("object"==typeof t){var e=t;if(void 0!==e.r||void 0!==e.red)this.setValues("rgb",e);else if(void 0!==e.l||void 0!==e.lightness)this.setValues("hsl",e);else if(void 0!==e.v||void 0!==e.value)this.setValues("hsv",e);else if(void 0!==e.w||void 0!==e.whiteness)this.setValues("hwb",e);else{if(void 0===e.c&&void 0===e.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(t));this.setValues("cmyk",e)}}};o.prototype={rgb:function(t){return this.setSpace("rgb",arguments)},hsl:function(t){return this.setSpace("hsl",arguments)},hsv:function(t){return this.setSpace("hsv",arguments)},hwb:function(t){return this.setSpace("hwb",arguments)},cmyk:function(t){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){return 1!==this.values.alpha?this.values.hwb.concat([this.values.alpha]):this.values.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values.rgb;return t.concat([this.values.alpha])},hslaArray:function(){var t=this.values.hsl;return t.concat([this.values.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return a.hexString(this.values.rgb)},rgbString:function(){return a.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return a.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return a.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return a.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return a.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return a.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return a.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){return this.values.rgb[0]<<16|this.values.rgb[1]<<8|this.values.rgb[2]},luminosity:function(){for(var t=this.values.rgb,e=[],i=0;i=s?s/12.92:Math.pow((s+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),i=t.luminosity();return e>i?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){return this.values.hsl[2]+=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},darken:function(t){return this.values.hsl[2]-=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},saturate:function(t){return this.values.hsl[1]+=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},desaturate:function(t){return this.values.hsl[1]-=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},whiten:function(t){return this.values.hwb[1]+=this.values.hwb[1]*t,this.setValues("hwb",this.values.hwb),this},blacken:function(t){return this.values.hwb[2]+=this.values.hwb[2]*t,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){return this.setValues("alpha",this.values.alpha-this.values.alpha*t),this},opaquer:function(t){return this.setValues("alpha",this.values.alpha+this.values.alpha*t),this},rotate:function(t){var e=this.values.hsl[0];return e=(e+t)%360,e=0>e?360+e:e,this.values.hsl[0]=e,this.setValues("hsl",this.values.hsl),this},mix:function(t,e){e=1-(null==e?.5:e);for(var i=2*e-1,s=this.alpha()-t.alpha(),a=((i*s==-1?i:(i+s)/(1+i*s))+1)/2,o=1-a,n=this.rgbArray(),r=t.rgbArray(),h=0;he&&(e+=360),s=(r+h)/2,i=h==r?0:.5>=s?l/(h+r):l/(2-h-r),[e,100*i,100*s]}function a(t){var e,i,s,a=t[0],o=t[1],n=t[2],r=Math.min(a,o,n),h=Math.max(a,o,n),l=h-r;return i=0==h?0:l/h*1e3/10,h==r?e=0:a==h?e=(o-n)/l:o==h?e=2+(n-a)/l:n==h&&(e=4+(a-o)/l),e=Math.min(60*e,360),0>e&&(e+=360),s=h/255*1e3/10,[e,i,s]}function o(t){var e=t[0],i=t[1],a=t[2],o=s(t)[0],n=1/255*Math.min(e,Math.min(i,a)),a=1-1/255*Math.max(e,Math.max(i,a));return[o,100*n,100*a]}function n(t){var e,i,s,a,o=t[0]/255,n=t[1]/255,r=t[2]/255;return a=Math.min(1-o,1-n,1-r),e=(1-o-a)/(1-a)||0,i=(1-n-a)/(1-a)||0,s=(1-r-a)/(1-a)||0,[100*e,100*i,100*s,100*a]}function h(t){return X[JSON.stringify(t)]}function l(t){var e=t[0]/255,i=t[1]/255,s=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,s=s>.04045?Math.pow((s+.055)/1.055,2.4):s/12.92;var a=.4124*e+.3576*i+.1805*s,o=.2126*e+.7152*i+.0722*s,n=.0193*e+.1192*i+.9505*s;return[100*a,100*o,100*n]}function c(t){var e,i,s,a=l(t),o=a[0],n=a[1],r=a[2];return o/=95.047,n/=100,r/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*n-16,i=500*(o-n),s=200*(n-r),[e,i,s]}function d(t){return B(c(t))}function u(t){var e,i,s,a,o,n=t[0]/360,r=t[1]/100,h=t[2]/100;if(0==r)return o=255*h,[o,o,o];i=.5>h?h*(1+r):h+r-h*r,e=2*h-i,a=[0,0,0];for(var l=0;3>l;l++)s=n+1/3*-(l-1),0>s&&s++,s>1&&s--,o=1>6*s?e+6*(i-e)*s:1>2*s?i:2>3*s?e+(i-e)*(2/3-s)*6:e,a[l]=255*o;return a}function f(t){var e,i,s=t[0],a=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,a*=1>=o?o:2-o,i=(o+a)/2,e=2*a/(o+a),[s,100*e,100*i])}function m(t){return o(u(t))}function p(t){return n(u(t))}function v(t){return h(u(t))}function x(t){var e=t[0]/60,i=t[1]/100,s=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),n=255*s*(1-i),r=255*s*(1-i*o),h=255*s*(1-i*(1-o)),s=255*s;switch(a){case 0:return[s,h,n];case 1:return[r,s,n];case 2:return[n,s,h];case 3:return[n,r,s];case 4:return[h,n,s];case 5:return[s,n,r]}}function y(t){var e,i,s=t[0],a=t[1]/100,o=t[2]/100;return i=(2-a)*o,e=a*o,e/=1>=i?i:2-i,e=e||0,i/=2,[s,100*e,100*i]}function k(t){return o(x(t))}function _(t){return n(x(t))}function S(t){return h(x(t))}function w(t){var e,i,s,a,o=t[0]/360,n=t[1]/100,h=t[2]/100,l=n+h;switch(l>1&&(n/=l,h/=l),e=Math.floor(6*o),i=1-h,s=6*o-e,0!=(1&e)&&(s=1-s),a=n+s*(i-n),e){default:case 6:case 0:r=i,g=a,b=n;break;case 1:r=a,g=i,b=n;break;case 2:r=n,g=i,b=a;break;case 3:r=n,g=a,b=i;break;case 4:r=a,g=n,b=i;break;case 5:r=i,g=n,b=a}return[255*r,255*g,255*b]}function D(t){return s(w(t))}function M(t){return a(w(t))}function C(t){return n(w(t))}function A(t){return h(w(t))}function I(t){var e,i,s,a=t[0]/100,o=t[1]/100,n=t[2]/100,r=t[3]/100;return e=1-Math.min(1,a*(1-r)+r),i=1-Math.min(1,o*(1-r)+r),s=1-Math.min(1,n*(1-r)+r),[255*e,255*i,255*s]}function P(t){return s(I(t))}function T(t){return a(I(t))}function F(t){return o(I(t))}function O(t){return h(I(t))}function V(t){var e,i,s,a=t[0]/100,o=t[1]/100,n=t[2]/100;return e=3.2406*a+-1.5372*o+n*-.4986,i=a*-.9689+1.8758*o+.0415*n,s=.0557*a+o*-.204+1.057*n,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,s=s>.0031308?1.055*Math.pow(s,1/2.4)-.055:s=12.92*s,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),s=Math.min(Math.max(0,s),1),[255*e,255*i,255*s]}function R(t){var e,i,s,a=t[0],o=t[1],n=t[2];return a/=95.047,o/=100,n/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*o-16,i=500*(a-o),s=200*(o-n),[e,i,s]}function W(t){return B(R(t))}function L(t){var e,i,s,a,o=t[0],n=t[1],r=t[2];return 8>=o?(i=100*o/903.3,a=7.787*(i/100)+16/116):(i=100*Math.pow((o+16)/116,3),a=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(n/500+a-16/116)/7.787:95.047*Math.pow(n/500+a,3),s=.008859>=s/108.883?s=108.883*(a-r/200-16/116)/7.787:108.883*Math.pow(a-r/200,3),[e,i,s]}function B(t){var e,i,s,a=t[0],o=t[1],n=t[2];return e=Math.atan2(n,o),i=360*e/2/Math.PI,0>i&&(i+=360),s=Math.sqrt(o*o+n*n),[a,s,i]}function z(t){return V(L(t))}function Y(t){var e,i,s,a=t[0],o=t[1],n=t[2];return s=n/360*2*Math.PI,e=o*Math.cos(s),i=o*Math.sin(s),[a,e,i]}function H(t){return L(Y(t))}function N(t){return z(Y(t))}function E(t){return J[t]}function U(t){return s(E(t))}function j(t){return a(E(t))}function G(t){return o(E(t))}function q(t){return n(E(t))}function Z(t){return c(E(t))}function Q(t){return l(E(t))}e.exports={rgb2hsl:s,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:n,rgb2keyword:h,rgb2xyz:l,rgb2lab:c,rgb2lch:d,hsl2rgb:u,hsl2hsv:f,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:v,hsv2rgb:x,hsv2hsl:y,hsv2hwb:k,hsv2cmyk:_,hsv2keyword:S,hwb2rgb:w,hwb2hsl:D,hwb2hsv:M,hwb2cmyk:C,hwb2keyword:A,cmyk2rgb:I,cmyk2hsl:P,cmyk2hsv:T,cmyk2hwb:F,cmyk2keyword:O,keyword2rgb:E,keyword2hsl:U,keyword2hsv:j,keyword2hwb:G,keyword2cmyk:q,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:V,xyz2lab:R,xyz2lch:W,lab2xyz:L,lab2rgb:z,lab2lch:B,lch2lab:Y,lch2xyz:H,lch2rgb:N};var J={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},X={};for(var $ in J)X[JSON.stringify(J[$])]=$},{}],4:[function(t,e,i){var s=t("./conversions"),a=function(){return new l};for(var o in s){a[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),s[t](e)}}(o);var n=/(\w+)2(\w+)/.exec(o),r=n[1],h=n[2];a[r]=a[r]||{},a[r][h]=a[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=s[t](e);if("string"==typeof i||void 0===i)return i;for(var a=0;a0)for(i in ls)s=ls[i],a=e[s],g(a)||(t[s]=a);return t}function p(t){m(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),cs===!1&&(cs=!0,i.updateOffset(this),cs=!1)}function b(t){return t instanceof p||null!=t&&null!=t._isAMomentObject}function v(t){return 0>t?Math.ceil(t):Math.floor(t)}function x(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=v(e)),i}function y(t,e,i){var s,a=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),n=0;for(s=0;a>s;s++)(i&&t[s]!==e[s]||!i&&x(t[s])!==x(e[s]))&&n++;return n+o}function k(t){i.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function _(t,e){var s=!0;return h(function(){return null!=i.deprecationHandler&&i.deprecationHandler(null,t),s&&(k(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),s=!1),e.apply(this,arguments)},e)}function S(t,e){null!=i.deprecationHandler&&i.deprecationHandler(t,e),ds[t]||(k(e),ds[t]=!0)}function w(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function D(t){return"[object Object]"===Object.prototype.toString.call(t)}function M(t){var e,i;for(i in t)e=t[i],w(e)?this[i]=e:this["_"+i]=e;this._config=t,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function C(t,e){var i,s=h({},t);for(i in e)r(e,i)&&(D(t[i])&&D(e[i])?(s[i]={},h(s[i],t[i]),h(s[i],e[i])):null!=e[i]?s[i]=e[i]:delete s[i]);return s}function A(t){null!=t&&this.set(t)}function I(t){return t?t.toLowerCase().replace("_","-"):t}function P(t){for(var e,i,s,a,o=0;o0;){if(s=T(a.slice(0,e).join("-")))return s;if(i&&i.length>=e&&y(a,i,!0)>=e-1)break;e--}o++}return null}function T(i){var s=null;if(!ms[i]&&"undefined"!=typeof e&&e&&e.exports)try{s=fs._abbr,t("./locale/"+i),F(s)}catch(a){}return ms[i]}function F(t,e){var i;return t&&(i=g(e)?R(t):O(t,e),i&&(fs=i)),fs._abbr}function O(t,e){return null!==e?(e.abbr=t,null!=ms[t]?(S("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),e=C(ms[t]._config,e)):null!=e.parentLocale&&(null!=ms[e.parentLocale]?e=C(ms[e.parentLocale]._config,e):S("parentLocaleUndefined","specified parentLocale is not defined yet")),ms[t]=new A(e),F(t),ms[t]):(delete ms[t],null)}function V(t,e){if(null!=e){var i;null!=ms[t]&&(e=C(ms[t]._config,e)),i=new A(e),i.parentLocale=ms[t],ms[t]=i,F(t)}else null!=ms[t]&&(null!=ms[t].parentLocale?ms[t]=ms[t].parentLocale:null!=ms[t]&&delete ms[t]);return ms[t]}function R(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return fs;if(!a(t)){if(e=T(t))return e;t=[t]}return P(t)}function W(){return us(ms)}function L(t,e){var i=t.toLowerCase();ps[i]=ps[i+"s"]=ps[e]=t}function B(t){return"string"==typeof t?ps[t]||ps[t.toLowerCase()]:void 0}function z(t){var e,i,s={};for(i in t)r(t,i)&&(e=B(i),e&&(s[e]=t[i]));return s}function Y(t,e){return function(s){return null!=s?(N(this,t,s),i.updateOffset(this,e),this):H(this,t)}}function H(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function N(t,e,i){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](i)}function E(t,e){var i;if("object"==typeof t)for(i in t)this.set(i,t[i]);else if(t=B(t),w(this[t]))return this[t](e);return this}function U(t,e,i){var s=""+Math.abs(t),a=e-s.length,o=t>=0;return(o?i?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+s}function j(t,e,i,s){var a=s;"string"==typeof s&&(a=function(){return this[s]()}),t&&(ys[t]=a),e&&(ys[e[0]]=function(){return U(a.apply(this,arguments),e[1],e[2])}),i&&(ys[i]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function G(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function q(t){var e,i,s=t.match(bs);for(e=0,i=s.length;i>e;e++)ys[s[e]]?s[e]=ys[s[e]]:s[e]=G(s[e]);return function(e){var a,o="";for(a=0;i>a;a++)o+=s[a]instanceof Function?s[a].call(e,t):s[a];return o}}function Z(t,e){return t.isValid()?(e=Q(e,t.localeData()),xs[e]=xs[e]||q(e),xs[e](t)):t.localeData().invalidDate()}function Q(t,e){function i(t){return e.longDateFormat(t)||t}var s=5;for(vs.lastIndex=0;s>=0&&vs.test(t);)t=t.replace(vs,i),vs.lastIndex=0,s-=1;return t}function J(t,e,i){Bs[t]=w(e)?e:function(t,s){return t&&i?i:e}}function X(t,e){return r(Bs,t)?Bs[t](e._strict,e._locale):new RegExp($(t))}function $(t){return K(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,s,a){return e||i||s||a}))}function K(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function tt(t,e){var i,s=e;for("string"==typeof t&&(t=[t]),"number"==typeof e&&(s=function(t,i){i[e]=x(t)}),i=0;is;++s)o=l([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(o,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(o,"").toLocaleLowerCase();return i?"MMM"===e?(a=gs.call(this._shortMonthsParse,n),-1!==a?a:null):(a=gs.call(this._longMonthsParse,n),-1!==a?a:null):"MMM"===e?(a=gs.call(this._shortMonthsParse,n),-1!==a?a:(a=gs.call(this._longMonthsParse,n),-1!==a?a:null)):(a=gs.call(this._longMonthsParse,n),-1!==a?a:(a=gs.call(this._shortMonthsParse,n),-1!==a?a:null))}function rt(t,e,i){var s,a,o;if(this._monthsParseExact)return nt.call(this,t,e,i);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;12>s;s++){if(a=l([2e3,s]),i&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(a,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(a,"").replace(".","")+"$","i")),i||this._monthsParse[s]||(o="^"+this.months(a,"")+"|^"+this.monthsShort(a,""),this._monthsParse[s]=new RegExp(o.replace(".",""),"i")),i&&"MMMM"===e&&this._longMonthsParse[s].test(t))return s;if(i&&"MMM"===e&&this._shortMonthsParse[s].test(t))return s;if(!i&&this._monthsParse[s].test(t))return s}}function ht(t,e){var i;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=x(e);else if(e=t.localeData().monthsParse(e),"number"!=typeof e)return t;return i=Math.min(t.date(),st(t.year(),e)), +t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,i),t}function lt(t){return null!=t?(ht(this,t),i.updateOffset(this,!0),this):H(this,"Month")}function ct(){return st(this.year(),this.month())}function dt(t){return this._monthsParseExact?(r(this,"_monthsRegex")||ft.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function ut(t){return this._monthsParseExact?(r(this,"_monthsRegex")||ft.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function ft(){function t(t,e){return e.length-t.length}var e,i,s=[],a=[],o=[];for(e=0;12>e;e++)i=l([2e3,e]),s.push(this.monthsShort(i,"")),a.push(this.months(i,"")),o.push(this.months(i,"")),o.push(this.monthsShort(i,""));for(s.sort(t),a.sort(t),o.sort(t),e=0;12>e;e++)s[e]=K(s[e]),a[e]=K(a[e]),o[e]=K(o[e]);this._monthsRegex=new RegExp("^("+o.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+s.join("|")+")","i")}function gt(t){var e,i=t._a;return i&&-2===d(t).overflow&&(e=i[Hs]<0||i[Hs]>11?Hs:i[Ns]<1||i[Ns]>st(i[Ys],i[Hs])?Ns:i[Es]<0||i[Es]>24||24===i[Es]&&(0!==i[Us]||0!==i[js]||0!==i[Gs])?Es:i[Us]<0||i[Us]>59?Us:i[js]<0||i[js]>59?js:i[Gs]<0||i[Gs]>999?Gs:-1,d(t)._overflowDayOfYear&&(Ys>e||e>Ns)&&(e=Ns),d(t)._overflowWeeks&&-1===e&&(e=qs),d(t)._overflowWeekday&&-1===e&&(e=Zs),d(t).overflow=e),t}function mt(t){var e,i,s,a,o,n,r=t._i,h=ta.exec(r)||ea.exec(r);if(h){for(d(t).iso=!0,e=0,i=sa.length;i>e;e++)if(sa[e][1].exec(h[1])){a=sa[e][0],s=sa[e][2]!==!1;break}if(null==a)return void(t._isValid=!1);if(h[3]){for(e=0,i=aa.length;i>e;e++)if(aa[e][1].exec(h[3])){o=(h[2]||" ")+aa[e][0];break}if(null==o)return void(t._isValid=!1)}if(!s&&null!=o)return void(t._isValid=!1);if(h[4]){if(!ia.exec(h[4]))return void(t._isValid=!1);n="Z"}t._f=a+(o||"")+(n||""),Pt(t)}else t._isValid=!1}function pt(t){var e=oa.exec(t._i);return null!==e?void(t._d=new Date(+e[1])):(mt(t),void(t._isValid===!1&&(delete t._isValid,i.createFromInputFallback(t))))}function bt(t,e,i,s,a,o,n){var r=new Date(t,e,i,s,a,o,n);return 100>t&&t>=0&&isFinite(r.getFullYear())&&r.setFullYear(t),r}function vt(t){var e=new Date(Date.UTC.apply(null,arguments));return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function xt(t){return yt(t)?366:365}function yt(t){return t%4===0&&t%100!==0||t%400===0}function kt(){return yt(this.year())}function _t(t,e,i){var s=7+e-i,a=(7+vt(t,0,s).getUTCDay()-e)%7;return-a+s-1}function St(t,e,i,s,a){var o,n,r=(7+i-s)%7,h=_t(t,s,a),l=1+7*(e-1)+r+h;return 0>=l?(o=t-1,n=xt(o)+l):l>xt(t)?(o=t+1,n=l-xt(t)):(o=t,n=l),{year:o,dayOfYear:n}}function wt(t,e,i){var s,a,o=_t(t.year(),e,i),n=Math.floor((t.dayOfYear()-o-1)/7)+1;return 1>n?(a=t.year()-1,s=n+Dt(a,e,i)):n>Dt(t.year(),e,i)?(s=n-Dt(t.year(),e,i),a=t.year()+1):(a=t.year(),s=n),{week:s,year:a}}function Dt(t,e,i){var s=_t(t,e,i),a=_t(t+1,e,i);return(xt(t)-s+a)/7}function Mt(t,e,i){return null!=t?t:null!=e?e:i}function Ct(t){var e=new Date(i.now());return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function At(t){var e,i,s,a,o=[];if(!t._d){for(s=Ct(t),t._w&&null==t._a[Ns]&&null==t._a[Hs]&&It(t),t._dayOfYear&&(a=Mt(t._a[Ys],s[Ys]),t._dayOfYear>xt(a)&&(d(t)._overflowDayOfYear=!0),i=vt(a,0,t._dayOfYear),t._a[Hs]=i.getUTCMonth(),t._a[Ns]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=o[e]=s[e];for(;7>e;e++)t._a[e]=o[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[Es]&&0===t._a[Us]&&0===t._a[js]&&0===t._a[Gs]&&(t._nextDay=!0,t._a[Es]=0),t._d=(t._useUTC?vt:bt).apply(null,o),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Es]=24)}}function It(t){var e,i,s,a,o,n,r,h;e=t._w,null!=e.GG||null!=e.W||null!=e.E?(o=1,n=4,i=Mt(e.GG,t._a[Ys],wt(Bt(),1,4).year),s=Mt(e.W,1),a=Mt(e.E,1),(1>a||a>7)&&(h=!0)):(o=t._locale._week.dow,n=t._locale._week.doy,i=Mt(e.gg,t._a[Ys],wt(Bt(),o,n).year),s=Mt(e.w,1),null!=e.d?(a=e.d,(0>a||a>6)&&(h=!0)):null!=e.e?(a=e.e+o,(e.e<0||e.e>6)&&(h=!0)):a=o),1>s||s>Dt(i,o,n)?d(t)._overflowWeeks=!0:null!=h?d(t)._overflowWeekday=!0:(r=St(i,s,a,o,n),t._a[Ys]=r.year,t._dayOfYear=r.dayOfYear)}function Pt(t){if(t._f===i.ISO_8601)return void mt(t);t._a=[],d(t).empty=!0;var e,s,a,o,n,r=""+t._i,h=r.length,l=0;for(a=Q(t._f,t._locale).match(bs)||[],e=0;e0&&d(t).unusedInput.push(n),r=r.slice(r.indexOf(s)+s.length),l+=s.length),ys[o]?(s?d(t).empty=!1:d(t).unusedTokens.push(o),it(o,s,t)):t._strict&&!s&&d(t).unusedTokens.push(o);d(t).charsLeftOver=h-l,r.length>0&&d(t).unusedInput.push(r),d(t).bigHour===!0&&t._a[Es]<=12&&t._a[Es]>0&&(d(t).bigHour=void 0),d(t).parsedDateParts=t._a.slice(0),d(t).meridiem=t._meridiem,t._a[Es]=Tt(t._locale,t._a[Es],t._meridiem),At(t),gt(t)}function Tt(t,e,i){var s;return null==i?e:null!=t.meridiemHour?t.meridiemHour(e,i):null!=t.isPM?(s=t.isPM(i),s&&12>e&&(e+=12),s||12!==e||(e=0),e):e}function Ft(t){var e,i,s,a,o;if(0===t._f.length)return d(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;ao)&&(s=o,i=e));h(t,i||e)}function Ot(t){if(!t._d){var e=z(t._i);t._a=n([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),At(t)}}function Vt(t){var e=new p(gt(Rt(t)));return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Rt(t){var e=t._i,i=t._f;return t._locale=t._locale||R(t._l),null===e||void 0===i&&""===e?f({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),b(e)?new p(gt(e)):(a(i)?Ft(t):i?Pt(t):o(e)?t._d=e:Wt(t),u(t)||(t._d=null),t))}function Wt(t){var e=t._i;void 0===e?t._d=new Date(i.now()):o(e)?t._d=new Date(e.valueOf()):"string"==typeof e?pt(t):a(e)?(t._a=n(e.slice(0),function(t){return parseInt(t,10)}),At(t)):"object"==typeof e?Ot(t):"number"==typeof e?t._d=new Date(e):i.createFromInputFallback(t)}function Lt(t,e,i,s,a){var o={};return"boolean"==typeof i&&(s=i,i=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=a,o._l=i,o._i=t,o._f=e,o._strict=s,Vt(o)}function Bt(t,e,i,s){return Lt(t,e,i,s,!1)}function zt(t,e){var i,s;if(1===e.length&&a(e[0])&&(e=e[0]),!e.length)return Bt();for(i=e[0],s=1;st&&(t=-t,i="-"),i+U(~~(t/60),2)+e+U(~~t%60,2)})}function jt(t,e){var i=(e||"").match(t)||[],s=i[i.length-1]||[],a=(s+"").match(ca)||["-",0,0],o=+(60*a[1])+x(a[2]);return"+"===a[0]?o:-o}function Gt(t,e){var s,a;return e._isUTC?(s=e.clone(),a=(b(t)||o(t)?t.valueOf():Bt(t).valueOf())-s.valueOf(),s._d.setTime(s._d.valueOf()+a),i.updateOffset(s,!1),s):Bt(t).local()}function qt(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Zt(t,e){var s,a=this._offset||0;return this.isValid()?null!=t?("string"==typeof t?t=jt(Rs,t):Math.abs(t)<16&&(t=60*t),!this._isUTC&&e&&(s=qt(this)),this._offset=t,this._isUTC=!0,null!=s&&this.add(s,"m"),a!==t&&(!e||this._changeInProgress?de(this,oe(t-a,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,i.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?a:qt(this):null!=t?this:NaN}function Qt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Jt(t){return this.utcOffset(0,t)}function Xt(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(qt(this),"m")),this}function $t(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(jt(Vs,this._i)),this}function Kt(t){return this.isValid()?(t=t?Bt(t).utcOffset():0,(this.utcOffset()-t)%60===0):!1}function te(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function ee(){if(!g(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),t=Rt(t),t._a){var e=t._isUTC?l(t._a):Bt(t._a);this._isDSTShifted=this.isValid()&&y(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function ie(){return this.isValid()?!this._isUTC:!1}function se(){return this.isValid()?this._isUTC:!1}function ae(){return this.isValid()?this._isUTC&&0===this._offset:!1}function oe(t,e){var i,s,a,o=t,n=null;return Et(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(n=da.exec(t))?(i="-"===n[1]?-1:1,o={y:0,d:x(n[Ns])*i,h:x(n[Es])*i,m:x(n[Us])*i,s:x(n[js])*i,ms:x(n[Gs])*i}):(n=ua.exec(t))?(i="-"===n[1]?-1:1,o={y:ne(n[2],i),M:ne(n[3],i),w:ne(n[4],i),d:ne(n[5],i),h:ne(n[6],i),m:ne(n[7],i),s:ne(n[8],i)}):null==o?o={}:"object"==typeof o&&("from"in o||"to"in o)&&(a=he(Bt(o.from),Bt(o.to)),o={},o.ms=a.milliseconds,o.M=a.months),s=new Nt(o),Et(t)&&r(t,"_locale")&&(s._locale=t._locale),s}function ne(t,e){var i=t&&parseFloat(t.replace(",","."));return(isNaN(i)?0:i)*e}function re(t,e){var i={milliseconds:0,months:0};return i.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(i.months,"M").isAfter(e)&&--i.months,i.milliseconds=+e-+t.clone().add(i.months,"M"),i}function he(t,e){var i;return t.isValid()&&e.isValid()?(e=Gt(e,t),t.isBefore(e)?i=re(t,e):(i=re(e,t),i.milliseconds=-i.milliseconds,i.months=-i.months),i):{milliseconds:0,months:0}}function le(t){return 0>t?-1*Math.round(-1*t):Math.round(t)}function ce(t,e){return function(i,s){var a,o;return null===s||isNaN(+s)||(S(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),o=i,i=s,s=o),i="string"==typeof i?+i:i,a=oe(i,s),de(this,a,t),this}}function de(t,e,s,a){var o=e._milliseconds,n=le(e._days),r=le(e._months);t.isValid()&&(a=null==a?!0:a,o&&t._d.setTime(t._d.valueOf()+o*s),n&&N(t,"Date",H(t,"Date")+n*s),r&&ht(t,H(t,"Month")+r*s),a&&i.updateOffset(t,n||r))}function ue(t,e){var i=t||Bt(),s=Gt(i,this).startOf("day"),a=this.diff(s,"days",!0),o=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse",n=e&&(w(e[o])?e[o]():e[o]);return this.format(n||this.localeData().calendar(o,this,Bt(i)))}function fe(){return new p(this)}function ge(t,e){var i=b(t)?t:Bt(t);return this.isValid()&&i.isValid()?(e=B(g(e)?"millisecond":e),"millisecond"===e?this.valueOf()>i.valueOf():i.valueOf()e-o?(i=t.clone().add(a-1,"months"),s=(e-o)/(o-i)):(i=t.clone().add(a+1,"months"),s=(e-o)/(i-o)),-(a+s)||0}function _e(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function Se(){var t=this.clone().utc();return 0o&&(e=o),Qe.call(this,t,e,i,s,a))}function Qe(t,e,i,s,a){var o=St(t,e,i,s,a),n=vt(o.year,0,o.dayOfYear);return this.year(n.getUTCFullYear()),this.month(n.getUTCMonth()),this.date(n.getUTCDate()),this}function Je(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function Xe(t){return wt(t,this._week.dow,this._week.doy).week}function $e(){return this._week.dow}function Ke(){return this._week.doy}function ti(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")}function ei(t){var e=wt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")}function ii(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function si(t,e){return a(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(e)?"format":"standalone"][t.day()]}function ai(t){return this._weekdaysShort[t.day()]}function oi(t){return this._weekdaysMin[t.day()]}function ni(t,e,i){var s,a,o,n=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;7>s;++s)o=l([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(o,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(o,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(o,"").toLocaleLowerCase();return i?"dddd"===e?(a=gs.call(this._weekdaysParse,n),-1!==a?a:null):"ddd"===e?(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:null):(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:null):"dddd"===e?(a=gs.call(this._weekdaysParse,n),-1!==a?a:(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:null))):"ddd"===e?(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:(a=gs.call(this._weekdaysParse,n),-1!==a?a:(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:null))):(a=gs.call(this._minWeekdaysParse,n),-1!==a?a:(a=gs.call(this._weekdaysParse,n),-1!==a?a:(a=gs.call(this._shortWeekdaysParse,n),-1!==a?a:null)))}function ri(t,e,i){var s,a,o;if(this._weekdaysParseExact)return ni.call(this,t,e,i);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;7>s;s++){if(a=l([2e3,1]).day(s),i&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(a,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(a,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(a,"").replace(".",".?")+"$","i")),this._weekdaysParse[s]||(o="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[s]=new RegExp(o.replace(".",""),"i")),i&&"dddd"===e&&this._fullWeekdaysParse[s].test(t))return s;if(i&&"ddd"===e&&this._shortWeekdaysParse[s].test(t))return s;if(i&&"dd"===e&&this._minWeekdaysParse[s].test(t))return s;if(!i&&this._weekdaysParse[s].test(t))return s}}function hi(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=ii(t,this.localeData()),this.add(t-e,"d")):e}function li(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")}function ci(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function di(t){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||gi.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex}function ui(t){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||gi.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}function fi(t){return this._weekdaysParseExact?(r(this,"_weekdaysRegex")||gi.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}function gi(){function t(t,e){return e.length-t.length}var e,i,s,a,o,n=[],r=[],h=[],c=[];for(e=0;7>e;e++)i=l([2e3,1]).day(e),s=this.weekdaysMin(i,""),a=this.weekdaysShort(i,""),o=this.weekdays(i,""),n.push(s),r.push(a),h.push(o),c.push(s),c.push(a),c.push(o);for(n.sort(t),r.sort(t),h.sort(t),c.sort(t),e=0;7>e;e++)r[e]=K(r[e]),h[e]=K(h[e]),c[e]=K(c[e]);this._weekdaysRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+n.join("|")+")","i")}function mi(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")}function pi(){return this.hours()%12||12}function bi(){return this.hours()||24}function vi(t,e){j(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function xi(t,e){return e._meridiemParse}function yi(t){return"p"===(t+"").toLowerCase().charAt(0)}function ki(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"}function _i(t,e){e[Gs]=x(1e3*("0."+t))}function Si(){return this._isUTC?"UTC":""}function wi(){return this._isUTC?"Coordinated Universal Time":""}function Di(t){return Bt(1e3*t)}function Mi(){return Bt.apply(null,arguments).parseZone()}function Ci(t,e,i){var s=this._calendar[t];return w(s)?s.call(e,i):s}function Ai(t){var e=this._longDateFormat[t],i=this._longDateFormat[t.toUpperCase()];return e||!i?e:(this._longDateFormat[t]=i.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function Ii(){return this._invalidDate}function Pi(t){return this._ordinal.replace("%d",t)}function Ti(t){return t}function Fi(t,e,i,s){var a=this._relativeTime[i];return w(a)?a(t,e,i,s):a.replace(/%d/i,t)}function Oi(t,e){var i=this._relativeTime[t>0?"future":"past"];return w(i)?i(e):i.replace(/%s/i,e)}function Vi(t,e,i,s){var a=R(),o=l().set(s,e);return a[i](o,t)}function Ri(t,e,i){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return Vi(t,e,i,"month");var s,a=[];for(s=0;12>s;s++)a[s]=Vi(t,s,i,"month");return a}function Wi(t,e,i,s){"boolean"==typeof t?("number"==typeof e&&(i=e,e=void 0),e=e||""):(e=t,i=e,t=!1,"number"==typeof e&&(i=e,e=void 0),e=e||"");var a=R(),o=t?a._week.dow:0;if(null!=i)return Vi(e,(i+o)%7,s,"day");var n,r=[];for(n=0;7>n;n++)r[n]=Vi(e,(n+o)%7,s,"day");return r}function Li(t,e){return Ri(t,e,"months")}function Bi(t,e){return Ri(t,e,"monthsShort")}function zi(t,e,i){return Wi(t,e,i,"weekdays")}function Yi(t,e,i){return Wi(t,e,i,"weekdaysShort")}function Hi(t,e,i){return Wi(t,e,i,"weekdaysMin")}function Ni(){var t=this._data;return this._milliseconds=za(this._milliseconds),this._days=za(this._days),this._months=za(this._months),t.milliseconds=za(t.milliseconds),t.seconds=za(t.seconds),t.minutes=za(t.minutes),t.hours=za(t.hours),t.months=za(t.months),t.years=za(t.years),this}function Ei(t,e,i,s){var a=oe(e,i);return t._milliseconds+=s*a._milliseconds,t._days+=s*a._days,t._months+=s*a._months,t._bubble()}function Ui(t,e){return Ei(this,t,e,1)}function ji(t,e){return Ei(this,t,e,-1)}function Gi(t){return 0>t?Math.floor(t):Math.ceil(t)}function qi(){var t,e,i,s,a,o=this._milliseconds,n=this._days,r=this._months,h=this._data;return o>=0&&n>=0&&r>=0||0>=o&&0>=n&&0>=r||(o+=864e5*Gi(Qi(r)+n),n=0,r=0),h.milliseconds=o%1e3,t=v(o/1e3),h.seconds=t%60,e=v(t/60),h.minutes=e%60,i=v(e/60),h.hours=i%24,n+=v(i/24),a=v(Zi(n)),r+=a,n-=Gi(Qi(a)),s=v(r/12),r%=12,h.days=n,h.months=r,h.years=s,this}function Zi(t){return 4800*t/146097}function Qi(t){return 146097*t/4800}function Ji(t){var e,i,s=this._milliseconds;if(t=B(t),"month"===t||"year"===t)return e=this._days+s/864e5,i=this._months+Zi(e),"month"===t?i:i/12;switch(e=this._days+Math.round(Qi(this._months)),t){case"week":return e/7+s/6048e5;case"day":return e+s/864e5;case"hour":return 24*e+s/36e5;case"minute":return 1440*e+s/6e4;case"second":return 86400*e+s/1e3;case"millisecond":return Math.floor(864e5*e)+s;default:throw new Error("Unknown unit "+t)}}function Xi(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*x(this._months/12)}function $i(t){return function(){return this.as(t)}}function Ki(t){return t=B(t),this[t+"s"]()}function ts(t){return function(){return this._data[t]}}function es(){return v(this.days()/7)}function is(t,e,i,s,a){return a.relativeTime(e||1,!!i,t,s)}function ss(t,e,i){var s=oe(t).abs(),a=eo(s.as("s")),o=eo(s.as("m")),n=eo(s.as("h")),r=eo(s.as("d")),h=eo(s.as("M")),l=eo(s.as("y")),c=a=o&&["m"]||o=n&&["h"]||n=r&&["d"]||r=h&&["M"]||h=l&&["y"]||["yy",l];return c[2]=e,c[3]=+t>0,c[4]=i,is.apply(null,c)}function as(t,e){return void 0===io[t]?!1:void 0===e?io[t]:(io[t]=e,!0)}function os(t){var e=this.localeData(),i=ss(this,!t,e);return t&&(i=e.pastFuture(+this,i)),e.postformat(i)}function ns(){var t,e,i,s=so(this._milliseconds)/1e3,a=so(this._days),o=so(this._months);t=v(s/60),e=v(t/60),s%=60,t%=60,i=v(o/12),o%=12;var n=i,r=o,h=a,l=e,c=t,d=s,u=this.asSeconds();return u?(0>u?"-":"")+"P"+(n?n+"Y":"")+(r?r+"M":"")+(h?h+"D":"")+(l||c||d?"T":"")+(l?l+"H":"")+(c?c+"M":"")+(d?d+"S":""):"P0D"}var rs,hs;hs=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),i=e.length>>>0,s=0;i>s;s++)if(s in e&&t.call(this,e[s],s,e))return!0;return!1};var ls=i.momentProperties=[],cs=!1,ds={};i.suppressDeprecationWarnings=!1,i.deprecationHandler=null;var us;us=Object.keys?Object.keys:function(t){var e,i=[];for(e in t)r(t,e)&&i.push(e);return i};var fs,gs,ms={},ps={},bs=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,vs=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,xs={},ys={},ks=/\d/,_s=/\d\d/,Ss=/\d{3}/,ws=/\d{4}/,Ds=/[+-]?\d{6}/,Ms=/\d\d?/,Cs=/\d\d\d\d?/,As=/\d\d\d\d\d\d?/,Is=/\d{1,3}/,Ps=/\d{1,4}/,Ts=/[+-]?\d{1,6}/,Fs=/\d+/,Os=/[+-]?\d+/,Vs=/Z|[+-]\d\d:?\d\d/gi,Rs=/Z|[+-]\d\d(?::?\d\d)?/gi,Ws=/[+-]?\d+(\.\d{1,3})?/,Ls=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Bs={},zs={},Ys=0,Hs=1,Ns=2,Es=3,Us=4,js=5,Gs=6,qs=7,Zs=8;gs=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e=t?""+t:"+"+t}),j(0,["YY",2],0,function(){return this.year()%100}),j(0,["YYYY",4],0,"year"),j(0,["YYYYY",5],0,"year"),j(0,["YYYYYY",6,!0],0,"year"),L("year","y"),J("Y",Os),J("YY",Ms,_s),J("YYYY",Ps,ws),J("YYYYY",Ts,Ds),J("YYYYYY",Ts,Ds),tt(["YYYYY","YYYYYY"],Ys),tt("YYYY",function(t,e){e[Ys]=2===t.length?i.parseTwoDigitYear(t):x(t)}),tt("YY",function(t,e){e[Ys]=i.parseTwoDigitYear(t)}),tt("Y",function(t,e){e[Ys]=parseInt(t,10)}),i.parseTwoDigitYear=function(t){return x(t)+(x(t)>68?1900:2e3)};var na=Y("FullYear",!0);i.ISO_8601=function(){};var ra=_("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Bt.apply(null,arguments);return this.isValid()&&t.isValid()?this>t?this:t:f()}),ha=_("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Bt.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:f()}),la=function(){return Date.now?Date.now():+new Date};Ut("Z",":"),Ut("ZZ",""),J("Z",Rs),J("ZZ",Rs),tt(["Z","ZZ"],function(t,e,i){i._useUTC=!0,i._tzm=jt(Rs,t)});var ca=/([\+\-]|\d\d)/gi;i.updateOffset=function(){};var da=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,ua=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;oe.fn=Nt.prototype;var fa=ce(1,"add"),ga=ce(-1,"subtract");i.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",i.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ma=_("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)});j(0,["gg",2],0,function(){return this.weekYear()%100}),j(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ee("gggg","weekYear"),Ee("ggggg","weekYear"),Ee("GGGG","isoWeekYear"),Ee("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),J("G",Os),J("g",Os),J("GG",Ms,_s),J("gg",Ms,_s),J("GGGG",Ps,ws),J("gggg",Ps,ws),J("GGGGG",Ts,Ds),J("ggggg",Ts,Ds),et(["gggg","ggggg","GGGG","GGGGG"],function(t,e,i,s){e[s.substr(0,2)]=x(t)}),et(["gg","GG"],function(t,e,s,a){e[a]=i.parseTwoDigitYear(t)}),j("Q",0,"Qo","quarter"),L("quarter","Q"),J("Q",ks),tt("Q",function(t,e){e[Hs]=3*(x(t)-1)}),j("w",["ww",2],"wo","week"),j("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),J("w",Ms),J("ww",Ms,_s),J("W",Ms),J("WW",Ms,_s),et(["w","ww","W","WW"],function(t,e,i,s){e[s.substr(0,1)]=x(t)});var pa={dow:0,doy:6};j("D",["DD",2],"Do","date"),L("date","D"),J("D",Ms),J("DD",Ms,_s),J("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),tt(["D","DD"],Ns),tt("Do",function(t,e){e[Ns]=x(t.match(Ms)[0],10)});var ba=Y("Date",!0);j("d",0,"do","day"),j("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),j("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),j("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),j("e",0,0,"weekday"),j("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),J("d",Ms),J("e",Ms),J("E",Ms),J("dd",function(t,e){return e.weekdaysMinRegex(t)}),J("ddd",function(t,e){return e.weekdaysShortRegex(t)}),J("dddd",function(t,e){return e.weekdaysRegex(t)}),et(["dd","ddd","dddd"],function(t,e,i,s){var a=i._locale.weekdaysParse(t,s,i._strict);null!=a?e.d=a:d(i).invalidWeekday=t}),et(["d","e","E"],function(t,e,i,s){e[s]=x(t)});var va="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),xa="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),ya="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ka=Ls,_a=Ls,Sa=Ls;j("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),J("DDD",Is),J("DDDD",Ss),tt(["DDD","DDDD"],function(t,e,i){i._dayOfYear=x(t)}),j("H",["HH",2],0,"hour"),j("h",["hh",2],0,pi),j("k",["kk",2],0,bi),j("hmm",0,0,function(){return""+pi.apply(this)+U(this.minutes(),2)}),j("hmmss",0,0,function(){return""+pi.apply(this)+U(this.minutes(),2)+U(this.seconds(),2)}),j("Hmm",0,0,function(){return""+this.hours()+U(this.minutes(),2)}),j("Hmmss",0,0,function(){return""+this.hours()+U(this.minutes(),2)+U(this.seconds(),2)}),vi("a",!0),vi("A",!1),L("hour","h"),J("a",xi),J("A",xi),J("H",Ms),J("h",Ms),J("HH",Ms,_s),J("hh",Ms,_s),J("hmm",Cs),J("hmmss",As),J("Hmm",Cs),J("Hmmss",As),tt(["H","HH"],Es),tt(["a","A"],function(t,e,i){i._isPm=i._locale.isPM(t),i._meridiem=t}),tt(["h","hh"],function(t,e,i){e[Es]=x(t),d(i).bigHour=!0}),tt("hmm",function(t,e,i){var s=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s)), +d(i).bigHour=!0}),tt("hmmss",function(t,e,i){var s=t.length-4,a=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s,2)),e[js]=x(t.substr(a)),d(i).bigHour=!0}),tt("Hmm",function(t,e,i){var s=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s))}),tt("Hmmss",function(t,e,i){var s=t.length-4,a=t.length-2;e[Es]=x(t.substr(0,s)),e[Us]=x(t.substr(s,2)),e[js]=x(t.substr(a))});var wa=/[ap]\.?m?\.?/i,Da=Y("Hours",!0);j("m",["mm",2],0,"minute"),L("minute","m"),J("m",Ms),J("mm",Ms,_s),tt(["m","mm"],Us);var Ma=Y("Minutes",!1);j("s",["ss",2],0,"second"),L("second","s"),J("s",Ms),J("ss",Ms,_s),tt(["s","ss"],js);var Ca=Y("Seconds",!1);j("S",0,0,function(){return~~(this.millisecond()/100)}),j(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),j(0,["SSS",3],0,"millisecond"),j(0,["SSSS",4],0,function(){return 10*this.millisecond()}),j(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),j(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),j(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),j(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),j(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),L("millisecond","ms"),J("S",Is,ks),J("SS",Is,_s),J("SSS",Is,Ss);var Aa;for(Aa="SSSS";Aa.length<=9;Aa+="S")J(Aa,Fs);for(Aa="S";Aa.length<=9;Aa+="S")tt(Aa,_i);var Ia=Y("Milliseconds",!1);j("z",0,0,"zoneAbbr"),j("zz",0,0,"zoneName");var Pa=p.prototype;Pa.add=fa,Pa.calendar=ue,Pa.clone=fe,Pa.diff=ye,Pa.endOf=Fe,Pa.format=we,Pa.from=De,Pa.fromNow=Me,Pa.to=Ce,Pa.toNow=Ae,Pa.get=E,Pa.invalidAt=He,Pa.isAfter=ge,Pa.isBefore=me,Pa.isBetween=pe,Pa.isSame=be,Pa.isSameOrAfter=ve,Pa.isSameOrBefore=xe,Pa.isValid=ze,Pa.lang=ma,Pa.locale=Ie,Pa.localeData=Pe,Pa.max=ha,Pa.min=ra,Pa.parsingFlags=Ye,Pa.set=E,Pa.startOf=Te,Pa.subtract=ga,Pa.toArray=We,Pa.toObject=Le,Pa.toDate=Re,Pa.toISOString=Se,Pa.toJSON=Be,Pa.toString=_e,Pa.unix=Ve,Pa.valueOf=Oe,Pa.creationData=Ne,Pa.year=na,Pa.isLeapYear=kt,Pa.weekYear=Ue,Pa.isoWeekYear=je,Pa.quarter=Pa.quarters=Je,Pa.month=lt,Pa.daysInMonth=ct,Pa.week=Pa.weeks=ti,Pa.isoWeek=Pa.isoWeeks=ei,Pa.weeksInYear=qe,Pa.isoWeeksInYear=Ge,Pa.date=ba,Pa.day=Pa.days=hi,Pa.weekday=li,Pa.isoWeekday=ci,Pa.dayOfYear=mi,Pa.hour=Pa.hours=Da,Pa.minute=Pa.minutes=Ma,Pa.second=Pa.seconds=Ca,Pa.millisecond=Pa.milliseconds=Ia,Pa.utcOffset=Zt,Pa.utc=Jt,Pa.local=Xt,Pa.parseZone=$t,Pa.hasAlignedHourOffset=Kt,Pa.isDST=te,Pa.isDSTShifted=ee,Pa.isLocal=ie,Pa.isUtcOffset=se,Pa.isUtc=ae,Pa.isUTC=ae,Pa.zoneAbbr=Si,Pa.zoneName=wi,Pa.dates=_("dates accessor is deprecated. Use date instead.",ba),Pa.months=_("months accessor is deprecated. Use month instead",lt),Pa.years=_("years accessor is deprecated. Use year instead",na),Pa.zone=_("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Qt);var Ta=Pa,Fa={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Oa={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Va="Invalid date",Ra="%d",Wa=/\d{1,2}/,La={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Ba=A.prototype;Ba._calendar=Fa,Ba.calendar=Ci,Ba._longDateFormat=Oa,Ba.longDateFormat=Ai,Ba._invalidDate=Va,Ba.invalidDate=Ii,Ba._ordinal=Ra,Ba.ordinal=Pi,Ba._ordinalParse=Wa,Ba.preparse=Ti,Ba.postformat=Ti,Ba._relativeTime=La,Ba.relativeTime=Fi,Ba.pastFuture=Oi,Ba.set=M,Ba.months=at,Ba._months=Js,Ba.monthsShort=ot,Ba._monthsShort=Xs,Ba.monthsParse=rt,Ba._monthsRegex=Ks,Ba.monthsRegex=ut,Ba._monthsShortRegex=$s,Ba.monthsShortRegex=dt,Ba.week=Xe,Ba._week=pa,Ba.firstDayOfYear=Ke,Ba.firstDayOfWeek=$e,Ba.weekdays=si,Ba._weekdays=va,Ba.weekdaysMin=oi,Ba._weekdaysMin=ya,Ba.weekdaysShort=ai,Ba._weekdaysShort=xa,Ba.weekdaysParse=ri,Ba._weekdaysRegex=ka,Ba.weekdaysRegex=di,Ba._weekdaysShortRegex=_a,Ba.weekdaysShortRegex=ui,Ba._weekdaysMinRegex=Sa,Ba.weekdaysMinRegex=fi,Ba.isPM=yi,Ba._meridiemParse=wa,Ba.meridiem=ki,F("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10,i=1===x(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+i}}),i.lang=_("moment.lang is deprecated. Use moment.locale instead.",F),i.langData=_("moment.langData is deprecated. Use moment.localeData instead.",R);var za=Math.abs,Ya=$i("ms"),Ha=$i("s"),Na=$i("m"),Ea=$i("h"),Ua=$i("d"),ja=$i("w"),Ga=$i("M"),qa=$i("y"),Za=ts("milliseconds"),Qa=ts("seconds"),Ja=ts("minutes"),Xa=ts("hours"),$a=ts("days"),Ka=ts("months"),to=ts("years"),eo=Math.round,io={s:45,m:45,h:22,d:26,M:11},so=Math.abs,ao=Nt.prototype;ao.abs=Ni,ao.add=Ui,ao.subtract=ji,ao.as=Ji,ao.asMilliseconds=Ya,ao.asSeconds=Ha,ao.asMinutes=Na,ao.asHours=Ea,ao.asDays=Ua,ao.asWeeks=ja,ao.asMonths=Ga,ao.asYears=qa,ao.valueOf=Xi,ao._bubble=qi,ao.get=Ki,ao.milliseconds=Za,ao.seconds=Qa,ao.minutes=Ja,ao.hours=Xa,ao.days=$a,ao.weeks=es,ao.months=Ka,ao.years=to,ao.humanize=os,ao.toISOString=ns,ao.toString=ns,ao.toJSON=ns,ao.locale=Ie,ao.localeData=Pe,ao.toIsoString=_("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ns),ao.lang=ma,j("X",0,0,"unix"),j("x",0,0,"valueOf"),J("x",Os),J("X",Ws),tt("X",function(t,e,i){i._d=new Date(1e3*parseFloat(t,10))}),tt("x",function(t,e,i){i._d=new Date(x(t))}),i.version="2.13.0",s(Bt),i.fn=Ta,i.min=Yt,i.max=Ht,i.now=la,i.utc=l,i.unix=Di,i.months=Li,i.isDate=o,i.locale=F,i.invalid=f,i.duration=oe,i.isMoment=b,i.weekdays=zi,i.parseZone=Mi,i.localeData=R,i.isDuration=Et,i.monthsShort=Bi,i.weekdaysMin=Hi,i.defineLocale=O,i.updateLocale=V,i.locales=W,i.weekdaysShort=Yi,i.normalizeUnits=B,i.relativeTimeThreshold=as,i.prototype=Ta;var oo=i;return oo})},{}],7:[function(t,e,i){var s=t("./core/core.js")();t("./core/core.helpers")(s),t("./core/core.element")(s),t("./core/core.animation")(s),t("./core/core.controller")(s),t("./core/core.datasetController")(s),t("./core/core.layoutService")(s),t("./core/core.legend")(s),t("./core/core.plugin.js")(s),t("./core/core.scale")(s),t("./core/core.scaleService")(s),t("./core/core.title")(s),t("./core/core.tooltip")(s),t("./controllers/controller.bar")(s),t("./controllers/controller.bubble")(s),t("./controllers/controller.doughnut")(s),t("./controllers/controller.line")(s),t("./controllers/controller.polarArea")(s),t("./controllers/controller.radar")(s),t("./scales/scale.category")(s),t("./scales/scale.linear")(s),t("./scales/scale.logarithmic")(s),t("./scales/scale.radialLinear")(s),t("./scales/scale.time")(s),t("./elements/element.arc")(s),t("./elements/element.line")(s),t("./elements/element.point")(s),t("./elements/element.rectangle")(s),t("./charts/Chart.Bar")(s),t("./charts/Chart.Bubble")(s),t("./charts/Chart.Doughnut")(s),t("./charts/Chart.Line")(s),t("./charts/Chart.PolarArea")(s),t("./charts/Chart.Radar")(s),t("./charts/Chart.Scatter")(s),window.Chart=e.exports=s},{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.plugin.js":29,"./core/core.scale":30,"./core/core.scaleService":31,"./core/core.title":32,"./core/core.tooltip":33,"./elements/element.arc":34,"./elements/element.line":35,"./elements/element.point":36,"./elements/element.rectangle":37,"./scales/scale.category":38,"./scales/scale.linear":39,"./scales/scale.logarithmic":40,"./scales/scale.radialLinear":41,"./scales/scale.time":42}],8:[function(t,e,i){"use strict";e.exports=function(t){t.Bar=function(e,i){return i.type="bar",new t(e,i)}}},{}],9:[function(t,e,i){"use strict";e.exports=function(t){t.Bubble=function(e,i){return i.type="bubble",new t(e,i)}}},{}],10:[function(t,e,i){"use strict";e.exports=function(t){t.Doughnut=function(e,i){return i.type="doughnut",new t(e,i)}}},{}],11:[function(t,e,i){"use strict";e.exports=function(t){t.Line=function(e,i){return i.type="line",new t(e,i)}}},{}],12:[function(t,e,i){"use strict";e.exports=function(t){t.PolarArea=function(e,i){return i.type="polarArea",new t(e,i)}}},{}],13:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={aspectRatio:1};t.Radar=function(s,a){return a.options=e.configMerge(i,a.options),a.type="radar",new t(s,a)}}},{}],14:[function(t,e,i){"use strict";e.exports=function(t){var e={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-1"}],yAxes:[{type:"linear",position:"left",id:"y-axis-1"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){return"("+t.xLabel+", "+t.yLabel+")"}}}};t.defaults.scatter=e,t.controllers.scatter=t.controllers.line,t.Scatter=function(e,i){return i.type="scatter",new t(e,i)}}},{}],15:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bar={hover:{mode:"label"},scales:{xAxes:[{type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}},t.controllers.bar=t.DatasetController.extend({initialize:function(e,i){t.DatasetController.prototype.initialize.call(this,e,i),this.getMeta().bar=!0},getBarCount:function(){var t=0;return e.each(this.chart.data.datasets,function(e,i){var s=this.chart.getDatasetMeta(i);s.bar&&this.chart.isDatasetVisible(i)&&++t},this),t},addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:e}),s=this.getBarCount();this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0,s)},update:function(t){var i=this.getBarCount();e.each(this.getMeta().data,function(e,s){this.updateElement(e,s,t,i)},this)},updateElement:function(t,i,s,a){var o,n=this.getMeta(),r=this.getScaleForId(n.xAxisID),h=this.getScaleForId(n.yAxisID);o=h.min<0&&h.max<0?h.getPixelForValue(h.max):h.min>0&&h.max>0?h.getPixelForValue(h.min):h.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:r,_yScale:h,_datasetIndex:this.index,_index:i,_model:{x:this.calculateBarX(i,this.index),y:s?o:this.calculateBarY(i,this.index),label:this.chart.data.labels[i],datasetLabel:this.getDataset().label,base:s?o:this.calculateBarBase(this.index,i),width:this.calculateBarWidth(a),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),borderSkipped:t.custom&&t.custom.borderSkipped?t.custom.borderSkipped:this.chart.options.elements.rectangle.borderSkipped,borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}}),t.pivot()},calculateBarBase:function(t,e){var i=this.getMeta(),s=(this.getScaleForId(i.xAxisID),this.getScaleForId(i.yAxisID)),a=0;if(s.options.stacked){var o=this.chart.data.datasets[t].data[e];if(0>o)for(var n=0;t>n;n++){var r=this.chart.data.datasets[n],h=this.chart.getDatasetMeta(n);h.bar&&h.yAxisID===s.id&&this.chart.isDatasetVisible(n)&&(a+=r.data[e]<0?r.data[e]:0)}else for(var l=0;t>l;l++){var c=this.chart.data.datasets[l],d=this.chart.getDatasetMeta(l);d.bar&&d.yAxisID===s.id&&this.chart.isDatasetVisible(l)&&(a+=c.data[e]>0?c.data[e]:0)}return s.getPixelForValue(a)}return a=s.getPixelForValue(s.min),s.beginAtZero||s.min<=0&&s.max>=0||s.min>=0&&s.max<=0?a=s.getPixelForValue(0,0):s.min<0&&s.max<0&&(a=s.getPixelForValue(s.max)),a},getRuler:function(){var t=this.getMeta(),e=this.getScaleForId(t.xAxisID),i=(this.getScaleForId(t.yAxisID),this.getBarCount()),s=function(){for(var t=e.getPixelForTick(1)-e.getPixelForTick(0),i=2;ii;++i)e=this.chart.getDatasetMeta(i),e.bar&&this.chart.isDatasetVisible(i)&&++s;return s},calculateBarX:function(t,e){var i=this.getMeta(),s=(this.getScaleForId(i.yAxisID),this.getScaleForId(i.xAxisID)),a=this.getBarIndex(e),o=this.getRuler(),n=s.getPixelForValue(null,t,e,this.chart.isCombo);return n-=this.chart.isCombo?o.tickWidth/2:0,s.options.stacked?n+o.categoryWidth/2+o.categorySpacing:n+o.barWidth/2+o.categorySpacing+o.barWidth*a+o.barSpacing/2+o.barSpacing*a},calculateBarY:function(t,e){var i=this.getMeta(),s=(this.getScaleForId(i.xAxisID),this.getScaleForId(i.yAxisID)),a=this.getDataset().data[t];if(s.options.stacked){for(var o=0,n=0,r=0;e>r;r++){var h=this.chart.data.datasets[r],l=this.chart.getDatasetMeta(r);l.bar&&l.yAxisID===s.id&&this.chart.isDatasetVisible(r)&&(h.data[t]<0?n+=h.data[t]||0:o+=h.data[t]||0)}return 0>a?s.getPixelForValue(n+a):s.getPixelForValue(o+a)}return s.getPixelForValue(a)},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){var s=this.getDataset().data[e];null===s||void 0===s||isNaN(s)||t.transition(i).draw()},this)},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)}}),t.defaults.horizontalBar={hover:{mode:"label"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{position:"left",type:"category",categoryPercentage:.8,barPercentage:.9,gridLines:{offsetGridLines:!0}}]}},t.controllers.horizontalBar=t.controllers.bar.extend({updateElement:function(t,i,s,a){var o,n=this.getMeta(),r=this.getScaleForId(n.xAxisID),h=this.getScaleForId(n.yAxisID);o=r.min<0&&r.max<0?r.getPixelForValue(r.max):r.min>0&&r.max>0?r.getPixelForValue(r.min):r.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:r,_yScale:h,_datasetIndex:this.index,_index:i,_model:{x:s?o:this.calculateBarX(i,this.index),y:this.calculateBarY(i,this.index),label:this.chart.data.labels[i],datasetLabel:this.getDataset().label,base:s?o:this.calculateBarBase(this.index,i),height:this.calculateBarHeight(a),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.rectangle.backgroundColor),borderSkipped:t.custom&&t.custom.borderSkipped?t.custom.borderSkipped:this.chart.options.elements.rectangle.borderSkipped,borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.rectangle.borderWidth)},draw:function(){function t(t){return h[(c+t)%4]}var e=this._chart.ctx,i=this._view,s=i.height/2,a=i.y-s,o=i.y+s,n=i.base-(i.base-i.x),r=i.borderWidth/2;i.borderWidth&&(a+=r,o-=r,n+=r),e.beginPath(),e.fillStyle=i.backgroundColor,e.strokeStyle=i.borderColor,e.lineWidth=i.borderWidth;var h=[[i.base,o],[i.base,a],[n,a],[n,o]],l=["bottom","left","top","right"],c=l.indexOf(i.borderSkipped,0);-1===c&&(c=0),e.moveTo.apply(e,t(0));for(var d=1;4>d;d++)e.lineTo.apply(e,t(d));e.fill(),i.borderWidth&&e.stroke()},inRange:function(t,e){var i=this._view,s=!1;return i&&(s=i.x=i.y-i.height/2&&e<=i.y+i.height/2&&t>=i.x&&t<=i.base:e>=i.y-i.height/2&&e<=i.y+i.height/2&&t>=i.base&&t<=i.x),s}}),t.pivot()},calculateBarBase:function(t,e){var i=this.getMeta(),s=this.getScaleForId(i.xAxisID),a=(this.getScaleForId(i.yAxisID),0);if(s.options.stacked){var o=this.chart.data.datasets[t].data[e];if(0>o)for(var n=0;t>n;n++){var r=this.chart.data.datasets[n],h=this.chart.getDatasetMeta(n);h.bar&&h.xAxisID===s.id&&this.chart.isDatasetVisible(n)&&(a+=r.data[e]<0?r.data[e]:0)}else for(var l=0;t>l;l++){var c=this.chart.data.datasets[l],d=this.chart.getDatasetMeta(l);d.bar&&d.xAxisID===s.id&&this.chart.isDatasetVisible(l)&&(a+=c.data[e]>0?c.data[e]:0)}return s.getPixelForValue(a)}return a=s.getPixelForValue(s.min),s.beginAtZero||s.min<=0&&s.max>=0||s.min>=0&&s.max<=0?a=s.getPixelForValue(0,0):s.min<0&&s.max<0&&(a=s.getPixelForValue(s.max)),a},getRuler:function(){var t=this.getMeta(),e=(this.getScaleForId(t.xAxisID),this.getScaleForId(t.yAxisID)),i=this.getBarCount(),s=function(){for(var t=e.getPixelForTick(1)-e.getPixelForTick(0),i=2;ir;r++){var h=this.chart.data.datasets[r],l=this.chart.getDatasetMeta(r);l.bar&&l.xAxisID===s.id&&this.chart.isDatasetVisible(r)&&(h.data[t]<0?n+=h.data[t]||0:o+=h.data[t]||0)}return 0>a?s.getPixelForValue(n+a):s.getPixelForValue(o+a)}return s.getPixelForValue(a)},calculateBarY:function(t,e){var i=this.getMeta(),s=this.getScaleForId(i.yAxisID),a=(this.getScaleForId(i.xAxisID),this.getBarIndex(e)),o=this.getRuler(),n=s.getPixelForValue(null,t,e,this.chart.isCombo);return n-=this.chart.isCombo?o.tickHeight/2:0,s.options.stacked?n+o.categoryHeight/2+o.categorySpacing:n+o.barHeight/2+o.categorySpacing+o.barHeight*a+o.barSpacing/2+o.barSpacing*a}})}},{}],16:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(t,e){return""},label:function(t,e){var i=e.datasets[t.datasetIndex].label||"",s=e.datasets[t.datasetIndex].data[t.index];return i+": ("+s.x+", "+s.y+", "+s.r+")"}}}},t.controllers.bubble=t.DatasetController.extend({addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0)},update:function(t){var i,s=this.getMeta(),a=s.data,o=this.getScaleForId(s.yAxisID);this.getScaleForId(s.xAxisID);i=o.min<0&&o.max<0?o.getPixelForValue(o.max):o.min>0&&o.max>0?o.getPixelForValue(o.min):o.getPixelForValue(0),e.each(a,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,s){var a,o=this.getMeta(),n=this.getScaleForId(o.yAxisID),r=this.getScaleForId(o.xAxisID);a=n.min<0&&n.max<0?n.getPixelForValue(n.max):n.min>0&&n.max>0?n.getPixelForValue(n.min):n.getPixelForValue(0),e.extend(t,{_chart:this.chart.chart,_xScale:r,_yScale:n,_datasetIndex:this.index,_index:i,_model:{x:s?r.getPixelForDecimal(.5):r.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:s?a:n.getPixelForValue(this.getDataset().data[i],i,this.index),radius:s?0:t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[i]),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y),t.pivot()},getRadius:function(t){return t.r||this.chart.options.elements.point.radius},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){t.transition(i),t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.hoverRadius,s,this.chart.options.elements.point.hoverRadius)+this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:this.getRadius(this.getDataset().data[t._index]),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],17:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.doughnut={animation:{animateRotate:!0,animateScale:!1},aspectRatio:1,hover:{mode:"single"},legendCallback:function(t){var e=[];if(e.push('
    '),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var i=t.data;return i.labels.length&&i.datasets.length?i.labels.map(function(s,a){var o=t.getDatasetMeta(0),n=i.datasets[0],r=o.data[a],h=r.custom&&r.custom.backgroundColor?r.custom.backgroundColor:e.getValueAtIndexOrDefault(n.backgroundColor,a,this.chart.options.elements.arc.backgroundColor),l=r.custom&&r.custom.borderColor?r.custom.borderColor:e.getValueAtIndexOrDefault(n.borderColor,a,this.chart.options.elements.arc.borderColor),c=r.custom&&r.custom.borderWidth?r.custom.borderWidth:e.getValueAtIndexOrDefault(n.borderWidth,a,this.chart.options.elements.arc.borderWidth);return{text:s,fillStyle:h,strokeStyle:l,lineWidth:c,hidden:isNaN(n.data[a])||o.data[a].hidden,index:a}},this):[]}},onClick:function(t,e){var i,s,a,o=e.index,n=this.chart;for(i=0,s=(n.data.datasets||[]).length;s>i;++i)a=n.getDatasetMeta(i),a.data[o].hidden=!a.data[o].hidden;n.update()}},cutoutPercentage:50,rotation:Math.PI*-.5,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+e.datasets[t.datasetIndex].data[t.index]}}}},t.defaults.pie=e.clone(t.defaults.doughnut),e.extend(t.defaults.pie,{cutoutPercentage:0}),t.controllers.doughnut=t.controllers.pie=t.DatasetController.extend({linkScales:function(){},addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(i,s){var a=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:i});s&&e.isArray(this.getDataset().backgroundColor)&&this.getDataset().backgroundColor.splice(i,0,s),this.getMeta().data.splice(i,0,a),this.updateElement(a,i,!0)},getRingIndex:function(t){for(var e=0,i=0;t>i;++i)this.chart.isDatasetVisible(i)&&++e;return e},update:function(t){var i=this.chart.chartArea.right-this.chart.chartArea.left-this.chart.options.elements.arc.borderWidth,s=this.chart.chartArea.bottom-this.chart.chartArea.top-this.chart.options.elements.arc.borderWidth,a=Math.min(i,s),o={x:0,y:0};if(this.chart.options.circumference<2*Math.PI){var n=this.chart.options.rotation%(2*Math.PI);n+=2*Math.PI*(n>=Math.PI?-1:n<-Math.PI?1:0);var r=n+this.chart.options.circumference,h={x:Math.cos(n),y:Math.sin(n)},l={x:Math.cos(r),y:Math.sin(r)},c=0>=n&&r>=0||n<=2*Math.PI&&2*Math.PI<=r,d=n<=.5*Math.PI&&.5*Math.PI<=r||n<=2.5*Math.PI&&2.5*Math.PI<=r,u=n<=-Math.PI&&-Math.PI<=r||n<=Math.PI&&Math.PI<=r,f=n<=.5*-Math.PI&&.5*-Math.PI<=r||n<=1.5*Math.PI&&1.5*Math.PI<=r,g=this.chart.options.cutoutPercentage/100,m={x:u?-1:Math.min(h.x*(h.x<0?1:g),l.x*(l.x<0?1:g)),y:f?-1:Math.min(h.y*(h.y<0?1:g),l.y*(l.y<0?1:g))},p={x:c?1:Math.max(h.x*(h.x>0?1:g),l.x*(l.x>0?1:g)),y:d?1:Math.max(h.y*(h.y>0?1:g),l.y*(l.y>0?1:g))},b={width:.5*(p.x-m.x),height:.5*(p.y-m.y)};a=Math.min(i/b.width,s/b.height),o={x:(p.x+m.x)*-.5,y:(p.y+m.y)*-.5}}this.chart.outerRadius=Math.max(a/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.chart.getVisibleDatasetCount(),this.chart.offsetX=o.x*this.chart.outerRadius,this.chart.offsetY=o.y*this.chart.outerRadius,this.getMeta().total=this.calculateTotal(),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.getRingIndex(this.index),this.innerRadius=this.outerRadius-this.chart.radiusLength,e.each(this.getMeta().data,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,s){var a=(this.chart.chartArea.left+this.chart.chartArea.right)/2,o=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,n=this.chart.options.rotation,r=this.chart.options.rotation,h=s&&this.chart.options.animation.animateRotate?0:t.hidden?0:this.calculateCircumference(this.getDataset().data[i])*(this.chart.options.circumference/(2*Math.PI)),l=s&&this.chart.options.animation.animateScale?0:this.innerRadius,c=s&&this.chart.options.animation.animateScale?0:this.outerRadius;e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_model:{x:a+this.chart.offsetX,y:o+this.chart.offsetY,startAngle:n,endAngle:r,circumference:h,outerRadius:c,innerRadius:l,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor,i,this.chart.options.elements.arc.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.getDataset().label,i,this.chart.data.labels[i])}}),s&&this.chart.options.animation.animateRotate||(0===i?t._model.startAngle=this.chart.options.rotation:t._model.startAngle=this.getMeta().data[i-1]._model.endAngle,t._model.endAngle=t._model.startAngle+t._model.circumference),t.pivot()},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},calculateTotal:function(){var t,i=this.getDataset(),s=this.getMeta(),a=0;return e.each(s.data,function(e,s){t=i.data[s],isNaN(t)||e.hidden||(a+=Math.abs(t))}),a},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0}})}},{}],18:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.line={showLines:!0,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}},t.controllers.line=t.DatasetController.extend({ +addElements:function(){var i=this.getMeta();i.dataset=i.dataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:i.data}),e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},update:function(t){var i,s=this.getMeta(),a=s.dataset,o=s.data,n=this.getScaleForId(s.yAxisID);this.getScaleForId(s.xAxisID);i=n.min<0&&n.max<0?n.getPixelForValue(n.max):n.min>0&&n.max>0?n.getPixelForValue(n.min):n.getPixelForValue(0),this.chart.options.showLines&&(a._scale=n,a._datasetIndex=this.index,a._children=o,void 0!==this.getDataset().tension&&void 0===this.getDataset().lineTension&&(this.getDataset().lineTension=this.getDataset().tension),a._model={tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().lineTension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,scaleTop:n.top,scaleBottom:n.bottom,scaleZero:i},a.pivot()),e.each(o,function(e,i){this.updateElement(e,i,t)},this),this.chart.options.showLines&&0!==this.chart.options.elements.line.tension&&this.updateBezierControlPoints()},getPointBackgroundColor:function(t,i){var s=this.chart.options.elements.point.backgroundColor,a=this.getDataset();return t.custom&&t.custom.backgroundColor?s=t.custom.backgroundColor:a.pointBackgroundColor?s=e.getValueAtIndexOrDefault(a.pointBackgroundColor,i,s):a.backgroundColor&&(s=a.backgroundColor),s},getPointBorderColor:function(t,i){var s=this.chart.options.elements.point.borderColor,a=this.getDataset();return t.custom&&t.custom.borderColor?s=t.custom.borderColor:a.pointBorderColor?s=e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,s):a.borderColor&&(s=a.borderColor),s},getPointBorderWidth:function(t,i){var s=this.chart.options.elements.point.borderWidth,a=this.getDataset();return t.custom&&void 0!==t.custom.borderWidth?s=t.custom.borderWidth:void 0!==a.pointBorderWidth?s=e.getValueAtIndexOrDefault(a.pointBorderWidth,i,s):void 0!==a.borderWidth&&(s=a.borderWidth),s},updateElement:function(t,i,s){var a,o=this.getMeta(),n=this.getScaleForId(o.yAxisID),r=this.getScaleForId(o.xAxisID);a=n.min<0&&n.max<0?n.getPixelForValue(n.max):n.min>0&&n.max>0?n.getPixelForValue(n.min):n.getPixelForValue(0),t._chart=this.chart.chart,t._xScale=r,t._yScale=n,t._datasetIndex=this.index,t._index=i,void 0!==this.getDataset().radius&&void 0===this.getDataset().pointRadius&&(this.getDataset().pointRadius=this.getDataset().radius),void 0!==this.getDataset().hitRadius&&void 0===this.getDataset().pointHitRadius&&(this.getDataset().pointHitRadius=this.getDataset().hitRadius),t._model={x:r.getPixelForValue(this.getDataset().data[i],i,this.index,this.chart.isCombo),y:s?a:this.calculatePointY(this.getDataset().data[i],i,this.index,this.chart.isCombo),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),backgroundColor:this.getPointBackgroundColor(t,i),borderColor:this.getPointBorderColor(t,i),borderWidth:this.getPointBorderWidth(t,i),tension:o.dataset._model?o.dataset._model.tension:0,hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().pointHitRadius,i,this.chart.options.elements.point.hitRadius)},t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},calculatePointY:function(t,e,i,s){var a=this.getMeta(),o=(this.getScaleForId(a.xAxisID),this.getScaleForId(a.yAxisID));if(o.options.stacked){for(var n=0,r=0,h=0;i>h;h++){var l=this.chart.data.datasets[h],c=this.chart.getDatasetMeta(h);"line"===c.type&&this.chart.isDatasetVisible(h)&&(l.data[e]<0?r+=l.data[e]||0:n+=l.data[e]||0)}return 0>t?o.getPixelForValue(r+t):o.getPixelForValue(n+t)}return o.getPixelForValue(t)},updateBezierControlPoints:function(){var t=this.getMeta();e.each(t.data,function(i,s){var a=e.splineCurve(e.previousItem(t.data,s)._model,i._model,e.nextItem(t.data,s)._model,t.dataset._model.tension);i._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i.pivot()},this)},draw:function(t){var i=this.getMeta(),s=t||1;e.each(i.data,function(t){t.transition(s)}),this.chart.options.showLines&&i.dataset.transition(s).draw(),e.each(i.data,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,s,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);void 0!==this.getDataset().radius&&void 0===this.getDataset().pointRadius&&(this.getDataset().pointRadius=this.getDataset().radius),t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=this.getPointBackgroundColor(t,i),t._model.borderColor=this.getPointBorderColor(t,i),t._model.borderWidth=this.getPointBorderWidth(t,i)}})}},{}],19:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0},animation:{animateRotate:!0,animateScale:!0},aspectRatio:1,legendCallback:function(t){var e=[];if(e.push('
    '),t.data.datasets.length)for(var i=0;i'),t.data.labels[i]&&e.push(t.data.labels[i]),e.push("");return e.push("
"),e.join("")},legend:{labels:{generateLabels:function(t){var i=t.data;return i.labels.length&&i.datasets.length?i.labels.map(function(s,a){var o=t.getDatasetMeta(0),n=i.datasets[0],r=o.data[a],h=r.custom&&r.custom.backgroundColor?r.custom.backgroundColor:e.getValueAtIndexOrDefault(n.backgroundColor,a,this.chart.options.elements.arc.backgroundColor),l=r.custom&&r.custom.borderColor?r.custom.borderColor:e.getValueAtIndexOrDefault(n.borderColor,a,this.chart.options.elements.arc.borderColor),c=r.custom&&r.custom.borderWidth?r.custom.borderWidth:e.getValueAtIndexOrDefault(n.borderWidth,a,this.chart.options.elements.arc.borderWidth);return{text:s,fillStyle:h,strokeStyle:l,lineWidth:c,hidden:isNaN(n.data[a])||o.data[a].hidden,index:a}},this):[]}},onClick:function(t,e){var i,s,a,o=e.index,n=this.chart;for(i=0,s=(n.data.datasets||[]).length;s>i;++i)a=n.getDatasetMeta(i),a.data[o].hidden=!a.data[o].hidden;n.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}},t.controllers.polarArea=t.DatasetController.extend({linkScales:function(){},addElements:function(){var i=this.getMeta();e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:s})},this)},addElementAndReset:function(e){var i=new t.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0)},update:function(t){var i=this.getMeta(),s=Math.min(this.chart.chartArea.right-this.chart.chartArea.left,this.chart.chartArea.bottom-this.chart.chartArea.top);this.chart.outerRadius=Math.max((s-this.chart.options.elements.arc.borderWidth/2)/2,0),this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0),this.chart.radiusLength=(this.chart.outerRadius-this.chart.innerRadius)/this.chart.getVisibleDatasetCount(),this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.index,this.innerRadius=this.outerRadius-this.chart.radiusLength,i.count=this.countVisibleElements(),e.each(i.data,function(e,i){this.updateElement(e,i,t)},this)},updateElement:function(t,i,s){for(var a=this.calculateCircumference(this.getDataset().data[i]),o=(this.chart.chartArea.left+this.chart.chartArea.right)/2,n=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,r=0,h=this.getMeta(),l=0;i>l;++l)isNaN(this.getDataset().data[l])||h.data[l].hidden||++r;var c=t.hidden?0:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),d=-.5*Math.PI+a*r,u=d+(t.hidden?0:a),f={x:o,y:n,innerRadius:0,outerRadius:this.chart.options.animation.animateScale?0:this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[i]),startAngle:this.chart.options.animation.animateRotate?Math.PI*-.5:d,endAngle:this.chart.options.animation.animateRotate?Math.PI*-.5:u,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])};e.extend(t,{_chart:this.chart.chart,_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:s?f:{x:o,y:n,innerRadius:0,outerRadius:c,startAngle:d,endAngle:u,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),label:e.getValueAtIndexOrDefault(this.chart.data.labels,i,this.chart.data.labels[i])}}),t.pivot()},draw:function(t){var i=t||1;e.each(this.getMeta().data,function(t,e){t.transition(i).draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.hoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.hoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.hoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,i,this.chart.options.elements.arc.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,i,this.chart.options.elements.arc.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,i,this.chart.options.elements.arc.borderWidth)},countVisibleElements:function(){var t=this.getDataset(),i=this.getMeta(),s=0;return e.each(i.data,function(e,i){isNaN(t.data[i])||e.hidden||s++}),s},calculateCircumference:function(t){var e=this.getMeta().count;return e>0&&!isNaN(t)?2*Math.PI/e:0}})}},{}],20:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.radar={scale:{type:"radialLinear"},elements:{line:{tension:0}}},t.controllers.radar=t.DatasetController.extend({linkScales:function(){},addElements:function(){var i=this.getMeta();i.dataset=i.dataset||new t.elements.Line({_chart:this.chart.chart,_datasetIndex:this.index,_points:i.data,_loop:!0}),e.each(this.getDataset().data,function(e,s){i.data[s]=i.data[s]||new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:s,_model:{x:0,y:0}})},this)},addElementAndReset:function(e){var i=new t.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:e});this.getMeta().data.splice(e,0,i),this.updateElement(i,e,!0),this.updateBezierControlPoints()},update:function(t){var i,s=this.getMeta(),a=s.dataset,o=s.data,n=this.chart.scale;i=n.min<0&&n.max<0?n.getPointPositionForValue(0,n.max):n.min>0&&n.max>0?n.getPointPositionForValue(0,n.min):n.getPointPositionForValue(0,0),void 0!==this.getDataset().tension&&void 0===this.getDataset().lineTension&&(this.getDataset().lineTension=this.getDataset().tension),e.extend(s.dataset,{_datasetIndex:this.index,_children:o,_model:{tension:a.custom&&a.custom.tension?a.custom.tension:e.getValueOrDefault(this.getDataset().lineTension,this.chart.options.elements.line.tension),backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,borderCapStyle:a.custom&&a.custom.borderCapStyle?a.custom.borderCapStyle:this.getDataset().borderCapStyle||this.chart.options.elements.line.borderCapStyle,borderDash:a.custom&&a.custom.borderDash?a.custom.borderDash:this.getDataset().borderDash||this.chart.options.elements.line.borderDash,borderDashOffset:a.custom&&a.custom.borderDashOffset?a.custom.borderDashOffset:this.getDataset().borderDashOffset||this.chart.options.elements.line.borderDashOffset,borderJoinStyle:a.custom&&a.custom.borderJoinStyle?a.custom.borderJoinStyle:this.getDataset().borderJoinStyle||this.chart.options.elements.line.borderJoinStyle,scaleTop:n.top,scaleBottom:n.bottom,scaleZero:i}}),s.dataset.pivot(),e.each(o,function(e,i){this.updateElement(e,i,t)},this),this.updateBezierControlPoints()},updateElement:function(t,i,s){var a=this.chart.scale.getPointPositionForValue(i,this.getDataset().data[i]);e.extend(t,{_datasetIndex:this.index,_index:i,_scale:this.chart.scale,_model:{x:s?this.chart.scale.xCenter:a.x,y:s?this.chart.scale.yCenter:a.y,tension:t.custom&&t.custom.tension?t.custom.tension:e.getValueOrDefault(this.getDataset().tension,this.chart.options.elements.line.tension),radius:t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().pointRadius,i,this.chart.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth),pointStyle:t.custom&&t.custom.pointStyle?t.custom.pointStyle:e.getValueAtIndexOrDefault(this.getDataset().pointStyle,i,this.chart.options.elements.point.pointStyle),hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:e.getValueAtIndexOrDefault(this.getDataset().hitRadius,i,this.chart.options.elements.point.hitRadius)}}),t._model.skip=t.custom&&t.custom.skip?t.custom.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.getMeta();e.each(t.data,function(i,s){var a=e.splineCurve(e.previousItem(t.data,s,!0)._model,i._model,e.nextItem(t.data,s,!0)._model,i._model.tension);i._model.controlPointPreviousX=Math.max(Math.min(a.previous.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointPreviousY=Math.max(Math.min(a.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i._model.controlPointNextX=Math.max(Math.min(a.next.x,this.chart.chartArea.right),this.chart.chartArea.left),i._model.controlPointNextY=Math.max(Math.min(a.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top),i.pivot()},this)},draw:function(t){var i=this.getMeta(),s=t||1;e.each(i.data,function(t,e){t.transition(s)}),i.dataset.transition(s).draw(),e.each(i.data,function(t){t.draw()})},setHoverStyle:function(t){var i=this.chart.data.datasets[t._datasetIndex],s=t._index;t._model.radius=t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:e.getValueAtIndexOrDefault(i.pointHoverRadius,s,this.chart.options.elements.point.hoverRadius),t._model.backgroundColor=t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(i.pointHoverBackgroundColor,s,e.getHoverColor(t._model.backgroundColor)),t._model.borderColor=t.custom&&t.custom.hoverBorderColor?t.custom.hoverBorderColor:e.getValueAtIndexOrDefault(i.pointHoverBorderColor,s,e.getHoverColor(t._model.borderColor)),t._model.borderWidth=t.custom&&t.custom.hoverBorderWidth?t.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(i.pointHoverBorderWidth,s,t._model.borderWidth)},removeHoverStyle:function(t){var i=(this.chart.data.datasets[t._datasetIndex],t._index);t._model.radius=t.custom&&t.custom.radius?t.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,i,this.chart.options.elements.point.radius),t._model.backgroundColor=t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,i,this.chart.options.elements.point.backgroundColor),t._model.borderColor=t.custom&&t.custom.borderColor?t.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,i,this.chart.options.elements.point.borderColor),t._model.borderWidth=t.custom&&t.custom.borderWidth?t.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,i,this.chart.options.elements.point.borderWidth)}})}},{}],21:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:e.noop,onComplete:e.noop},t.Animation=t.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,i,s){s||(t.animating=!0);for(var a=0;a1&&(e=Math.floor(this.dropFrames),this.dropFrames=this.dropFrames%1);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.onAnimationProgress&&this.animations[i].animationObject.onAnimationProgress.call&&this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].animationObject.currentStep===this.animations[i].animationObject.numSteps?(this.animations[i].animationObject.onAnimationComplete&&this.animations[i].animationObject.onAnimationComplete.call&&this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance,this.animations[i]),this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1)):++i;var s=Date.now(),a=(s-t)/this.frameDuration;this.dropFrames+=a,this.animations.length>0&&this.requestAnimationFrame()}}}},{}],22:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.types={},t.instances={},t.controllers={},t.Controller=function(i){return this.chart=i,this.config=i.config,this.options=this.config.options=e.configMerge(t.defaults.global,t.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Object.defineProperty(this,"data",{get:function(){return this.config.data}}),t.instances[this.id]=this,this.options.responsive&&this.resize(!0),this.initialize(),this},e.extend(t.Controller.prototype,{initialize:function(){return t.pluginService.notifyPlugins("beforeInit",[this]),this.bindEvents(),this.ensureScalesHaveIDs(),this.buildOrUpdateControllers(),this.buildScales(),this.buildSurroundingItems(),this.updateLayout(),this.resetElements(),this.initToolTip(),this.update(),t.pluginService.notifyPlugins("afterInit",[this]),this},clear:function(){return e.clear(this.chart),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var i=this.chart.canvas,s=e.getMaximumWidth(this.chart.canvas),a=this.options.maintainAspectRatio&&isNaN(this.chart.aspectRatio)===!1&&isFinite(this.chart.aspectRatio)&&0!==this.chart.aspectRatio?s/this.chart.aspectRatio:e.getMaximumHeight(this.chart.canvas),o=this.chart.width!==s||this.chart.height!==a;return o?(i.width=this.chart.width=s,i.height=this.chart.height=a,e.retinaScale(this.chart),t||(this.stop(),this.update(this.options.responsiveAnimationDuration)),this):this},ensureScalesHaveIDs:function(){var t="x-axis-",i="y-axis-";this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(e,i){e.id=e.id||t+i}),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(t,e){t.id=t.id||i+e}))},buildScales:function(){if(this.scales={},this.options.scales&&(this.options.scales.xAxes&&this.options.scales.xAxes.length&&e.each(this.options.scales.xAxes,function(i,s){var a=e.getValueOrDefault(i.type,"category"),o=t.scaleService.getScaleConstructor(a);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this),this.options.scales.yAxes&&this.options.scales.yAxes.length&&e.each(this.options.scales.yAxes,function(i,s){var a=e.getValueOrDefault(i.type,"linear"),o=t.scaleService.getScaleConstructor(a);if(o){var n=new o({ctx:this.chart.ctx,options:i,chart:this,id:i.id});this.scales[n.id]=n}},this)),this.options.scale){var i=t.scaleService.getScaleConstructor(this.options.scale.type);if(i){var s=new i({ctx:this.chart.ctx,options:this.options.scale,chart:this});this.scale=s,this.scales.radialScale=s}}t.scaleService.addScalesToLayout(this)},buildSurroundingItems:function(){this.options.title&&(this.titleBlock=new t.Title({ctx:this.chart.ctx,options:this.options.title,chart:this}),t.layoutService.addBox(this,this.titleBlock)),this.options.legend&&(this.legend=new t.Legend({ctx:this.chart.ctx,options:this.options.legend,chart:this}),t.layoutService.addBox(this,this.legend))},updateLayout:function(){t.layoutService.update(this,this.chart.width,this.chart.height)},buildOrUpdateControllers:function(){var i=[],s=[];if(e.each(this.data.datasets,function(e,a){var o=this.getDatasetMeta(a);o.type||(o.type=e.type||this.config.type),i.push(o.type),o.controller?o.controller.updateIndex(a):(o.controller=new t.controllers[o.type](this,a),s.push(o.controller))},this),i.length>1)for(var a=1;a0&&(e=this.getDatasetMeta(e[0]._datasetIndex).data),e},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var i=e._meta[this.id];return i||(i=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,i=this.data.datasets.length;i>e;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroy:function(){this.clear(),e.unbindEvents(this,this.events),e.removeResizeListener(this.chart.canvas.parentNode);var i=this.chart.canvas;i.width=this.chart.width,i.height=this.chart.height,void 0!==this.chart.originalDevicePixelRatio&&this.chart.ctx.scale(1/this.chart.originalDevicePixelRatio,1/this.chart.originalDevicePixelRatio),i.style.width=this.chart.originalCanvasStyleWidth,i.style.height=this.chart.originalCanvasStyleHeight,t.pluginService.notifyPlugins("destroy",[this]),delete t.instances[this.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)},initToolTip:function(){this.tooltip=new t.Tooltip({_chart:this.chart,_chartInstance:this,_data:this.data,_options:this.options},this)},bindEvents:function(){e.bindEvents(this,this.options.events,function(t){this.eventHandler(t)})},eventHandler:function(t){if(this.lastActive=this.lastActive||[],this.lastTooltipActive=this.lastTooltipActive||[],"mouseout"===t.type)this.active=[],this.tooltipActive=[];else{var i=this,s=function(e){switch(e){case"single":return i.getElementAtEvent(t);case"label":return i.getElementsAtEvent(t);case"dataset":return i.getDatasetAtEvent(t);default:return t}};this.active=s(this.options.hover.mode),this.tooltipActive=s(this.options.tooltips.mode)}if(this.options.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"===t.type||"click"===t.type)&&(this.options.onClick&&this.options.onClick.call(this,t,this.active),this.legend&&this.legend.handleEvent&&this.legend.handleEvent(t)),this.lastActive.length)switch(this.options.hover.mode){case"single":this.getDatasetMeta(this.lastActive[0]._datasetIndex).controller.removeHoverStyle(this.lastActive[0],this.lastActive[0]._datasetIndex,this.lastActive[0]._index);break;case"label":case"dataset":for(var a=0;ai)e.splice(i,s-i);else if(i>s)for(var a=s;i>a;++a)this.addElementAndReset(a)},addElements:i,addElementAndReset:i,draw:i,removeHoverStyle:i,setHoverStyle:i,update:i}),t.DatasetController.extend=e.inherits}},{}],24:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.elements={},t.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(t.Element.prototype,{initialize:function(){this.hidden=!1},pivot:function(){return this._view||(this._view=e.clone(this._model)),this._start=e.clone(this._view),this},transition:function(t){return this._view||(this._view=e.clone(this._model)),1===t?(this._view=this._model,this._start=null,this):(this._start||this.pivot(),e.each(this._model,function(i,s){if("_"!==s[0]&&this._model.hasOwnProperty(s))if(this._view.hasOwnProperty(s))if(i===this._view[s]);else if("string"==typeof i)try{var a=e.color(this._start[s]).mix(e.color(this._model[s]),t);this._view[s]=a.rgbString()}catch(o){this._view[s]=i}else if("number"==typeof i){var n=void 0!==this._start[s]&&isNaN(this._start[s])===!1?this._start[s]:0;this._view[s]=(this._model[s]-n)*t+n}else this._view[s]=i;else"number"!=typeof i||isNaN(this._view[s])?this._view[s]=i:this._view[s]=i*t;else;},this),this)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),t.Element.extend=e.inherits}},{}],25:[function(t,e,i){"use strict";var s=t("chartjs-color");e.exports=function(t){function e(t,e,i){var s;return"string"==typeof t?(s=parseInt(t,10),-1!=t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}function i(t,i,s){var a,o=document.defaultView.getComputedStyle(t)[i],n=document.defaultView.getComputedStyle(t.parentNode)[i],r=null!==o&&"none"!==o,h=null!==n&&"none"!==n;return(r||h)&&(a=Math.min(r?e(o,t,s):Number.POSITIVE_INFINITY,h?e(n,t.parentNode,s):Number.POSITIVE_INFINITY)),a}var a=t.helpers={};a.each=function(t,e,i,s){var o,n;if(a.isArray(t))if(n=t.length,s)for(o=n-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;n>o;o++)e.call(i,t[o],o);else if("object"==typeof t){var r=Object.keys(t);for(n=r.length,o=0;n>o;o++)e.call(i,t[r[o]],r[o])}},a.clone=function(t){var e={};return a.each(t,function(i,s){t.hasOwnProperty(s)&&(a.isArray(i)?e[s]=i.slice(0):"object"==typeof i&&null!==i?e[s]=a.clone(i):e[s]=i)}),e},a.extend=function(t){for(var e=arguments.length,i=[],s=1;e>s;s++)i.push(arguments[s]);return a.each(i,function(e){a.each(e,function(i,s){e.hasOwnProperty(s)&&(t[s]=i)})}),t},a.configMerge=function(e){var i=a.clone(e);return a.each(Array.prototype.slice.call(arguments,1),function(e){a.each(e,function(s,o){if(e.hasOwnProperty(o))if("scales"===o)i[o]=a.scaleMerge(i.hasOwnProperty(o)?i[o]:{},s);else if("scale"===o)i[o]=a.configMerge(i.hasOwnProperty(o)?i[o]:{},t.scaleService.getScaleDefaults(s.type),s);else if(i.hasOwnProperty(o)&&a.isArray(i[o])&&a.isArray(s)){var n=i[o];a.each(s,function(t,e){e=s[o].length||!s[o][i].type?s[o].push(a.configMerge(r,e)):e.type&&e.type!==s[o][i].type?s[o][i]=a.configMerge(s[o][i],r,e):s[o][i]=a.configMerge(s[o][i],e)}):(s[o]=[],a.each(e,function(e){var i=a.getValueOrDefault(e.type,"xAxes"===o?"category":"linear");s[o].push(a.configMerge(t.scaleService.getScaleDefaults(i),e))})):s.hasOwnProperty(o)&&"object"==typeof s[o]&&null!==s[o]&&"object"==typeof e?s[o]=a.configMerge(s[o],e):s[o]=e)}),s},a.getValueAtIndexOrDefault=function(t,e,i){return void 0===t||null===t?i:a.isArray(t)?e=0;s--){var a=t[s];if(e(a))return a}},a.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},s=function(){this.constructor=i};return s.prototype=e.prototype,i.prototype=new s,i.extend=a.inherits,t&&a.extend(i.prototype,t),i.__super__=e.prototype,i},a.noop=function(){},a.uid=function(){var t=0;return function(){return t++}}(),a.warn=function(t){console&&"function"==typeof console.warn&&console.warn(t)},a.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},a.almostEquals=function(t,e,i){return Math.abs(t-e)0?1:-1)},a.log10=function(t){return Math.log10?Math.log10(t):Math.log(t)/Math.LN10},a.toRadians=function(t){return t*(Math.PI/180)},a.toDegrees=function(t){return t*(180/Math.PI)},a.getAngleFromPoint=function(t,e){var i=e.x-t.x,s=e.y-t.y,a=Math.sqrt(i*i+s*s),o=Math.atan2(s,i);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},a.aliasPixel=function(t){return t%2===0?0:.5},a.splineCurve=function(t,e,i,s){var a=t.skip?e:t,o=e,n=i.skip?e:i,r=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),h=Math.sqrt(Math.pow(n.x-o.x,2)+Math.pow(n.y-o.y,2)),l=r/(r+h),c=h/(r+h);l=isNaN(l)?0:l,c=isNaN(c)?0:c;var d=s*l,u=s*c;return{previous:{x:o.x-d*(n.x-a.x),y:o.y-d*(n.y-a.y)},next:{x:o.x+u*(n.x-a.x),y:o.y+u*(n.y-a.y)}}},a.nextItem=function(t,e,i){return i?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},a.previousItem=function(t,e,i){return i?0>=e?t[t.length-1]:t[e-1]:0>=e?t[0]:t[e-1]},a.niceNum=function(t,e){var i,s=Math.floor(a.log10(t)),o=t/Math.pow(10,s);return i=e?1.5>o?1:3>o?2:7>o?5:10:1>=o?1:2>=o?2:5>=o?5:10,i*Math.pow(10,s)};var o=a.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,s=1;return 0===t?0:1===(t/=1)?1:(i||(i=.3),st?-.5*(s*Math.pow(2,10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)):s*Math.pow(2,-10*(t-=1))*Math.sin((1*t-e)*(2*Math.PI)/i)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-o.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?1*(7.5625*t*t):2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*o.easeInBounce(2*t):.5*o.easeOutBounce(2*t-1)+.5}};a.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),a.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),a.getRelativePosition=function(t,e){var i,s,o=t.originalEvent||t,n=t.currentTarget||t.srcElement,r=n.getBoundingClientRect();o.touches&&o.touches.length>0?(i=o.touches[0].clientX,s=o.touches[0].clientY):(i=o.clientX,s=o.clientY);var h=parseFloat(a.getStyle(n,"padding-left")),l=parseFloat(a.getStyle(n,"padding-top")),c=parseFloat(a.getStyle(n,"padding-right")),d=parseFloat(a.getStyle(n,"padding-bottom")),u=r.right-r.left-h-c,f=r.bottom-r.top-l-d;return i=Math.round((i-r.left-h)/u*n.width/e.currentDevicePixelRatio),s=Math.round((s-r.top-l)/f*n.height/e.currentDevicePixelRatio),{x:i,y:s}},a.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i},a.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=a.noop},a.bindEvents=function(t,e,i){t.events||(t.events={}),a.each(e,function(e){t.events[e]=function(){i.apply(t,arguments)},a.addEvent(t.chart.canvas,e,t.events[e])})},a.unbindEvents=function(t,e){a.each(e,function(e,i){a.removeEvent(t.chart.canvas,i,e)})},a.getConstraintWidth=function(t){return i(t,"max-width","clientWidth")},a.getConstraintHeight=function(t){return i(t,"max-height","clientHeight")},a.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(a.getStyle(e,"padding-left"))+parseInt(a.getStyle(e,"padding-right")),s=e.clientWidth-i,o=a.getConstraintWidth(t);return void 0!==o&&(s=Math.min(s,o)),s},a.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(a.getStyle(e,"padding-top"))+parseInt(a.getStyle(e,"padding-bottom")),s=e.clientHeight-i,o=a.getConstraintHeight(t);return void 0!==o&&(s=Math.min(s,o)),s},a.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},a.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,s=t.canvas.height,a=t.currentDevicePixelRatio=window.devicePixelRatio||1;1!==a&&(e.canvas.height=s*a,e.canvas.width=i*a,e.scale(a,a),t.originalDevicePixelRatio=t.originalDevicePixelRatio||a),e.canvas.style.width=i+"px",e.canvas.style.height=s+"px"},a.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},a.fontString=function(t,e,i){return e+" "+t+"px "+i},a.longestText=function(t,e,i,s){s=s||{},s.data=s.data||{},s.garbageCollect=s.garbageCollect||[],s.font!==e&&(s.data={},s.garbageCollect=[],s.font=e),t.font=e;var o=0;a.each(i,function(e){if(void 0!==e&&null!==e){var i=s.data[e];i||(i=s.data[e]=t.measureText(e).width,s.garbageCollect.push(e)),i>o&&(o=i)}});var n=s.garbageCollect.length/2;if(n>i.length){for(var r=0;n>r;r++)delete s.data[s.garbageCollect[r]];s.garbageCollect.splice(0,n)}return o},a.drawRoundedRectangle=function(t,e,i,s,a,o){t.beginPath(),t.moveTo(e+o,i),t.lineTo(e+s-o,i),t.quadraticCurveTo(e+s,i,e+s,i+o),t.lineTo(e+s,i+a-o),t.quadraticCurveTo(e+s,i+a,e+s-o,i+a),t.lineTo(e+o,i+a),t.quadraticCurveTo(e,i+a,e,i+a-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath()},a.color=function(e){return s?s(e instanceof CanvasGradient?t.defaults.global.defaultColor:e):(console.log("Color.js not found!"),e)},a.addResizeListener=function(t,e){var i=document.createElement("iframe"),s="chartjs-hidden-iframe";i.classlist?i.classlist.add(s):i.setAttribute("class",s),i.style.width="100%",i.style.display="block",i.style.border=0,i.style.height=0,i.style.margin=0,i.style.position="absolute",i.style.left=0,i.style.right=0,i.style.top=0,i.style.bottom=0,t.insertBefore(i,t.firstChild),(i.contentWindow||i).onresize=function(){e&&e()}},a.removeResizeListener=function(t){var e=t.querySelector(".chartjs-hidden-iframe");e&&e.parentNode.removeChild(e)},a.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(t)},a.pushAllIfDefined=function(t,e){"undefined"!=typeof t&&(a.isArray(t)?e.push.apply(e,t):e.push(t))},a.callCallback=function(t,e,i){t&&"function"==typeof t.call&&t.apply(i,e)},a.getHoverColor=function(t){return t instanceof CanvasPattern?t:a.color(t).saturate(.5).darken(.1).rgbString()}}},{"chartjs-color":2}],26:[function(t,e,i){"use strict";e.exports=function(){var t=function(e,i){this.config=i,e.length&&e[0].getContext&&(e=e[0]),e.getContext&&(e=e.getContext("2d")),this.ctx=e,this.canvas=e.canvas,this.width=e.canvas.width||parseInt(t.helpers.getStyle(e.canvas,"width"))||t.helpers.getMaximumWidth(e.canvas),this.height=e.canvas.height||parseInt(t.helpers.getStyle(e.canvas,"height"))||t.helpers.getMaximumHeight(e.canvas),this.aspectRatio=this.width/this.height,(isNaN(this.aspectRatio)||isFinite(this.aspectRatio)===!1)&&(this.aspectRatio=void 0!==i.aspectRatio?i.aspectRatio:2),this.originalCanvasStyleWidth=e.canvas.style.width,this.originalCanvasStyleHeight=e.canvas.style.height,t.helpers.retinaScale(this),i&&(this.controller=new t.Controller(this));var s=this;return t.helpers.addResizeListener(e.canvas.parentNode,function(){s.controller&&s.controller.config.options.responsive&&s.controller.resize()}),this.controller?this.controller:this};return t.defaults={global:{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},legendCallback:function(t){var e=[];e.push('
    ');for(var i=0;i'),t.data.datasets[i].label&&e.push(t.data.datasets[i].label),e.push("");return e.push("
"),e.join("")}}},t}},{}],27:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),t.boxes.push(e)},removeBox:function(t,e){t.boxes&&t.boxes.splice(t.boxes.indexOf(e),1)},update:function(t,i,s){function a(t){var e,i=t.isHorizontal();i?(e=t.update(t.options.fullWidth?m:k,y),_-=e.height):(e=t.update(x,v),k-=e.width),S.push({horizontal:i,minSize:e,box:t})}function o(t){var i=e.findNextWhere(S,function(e){return e.box===t});if(i)if(t.isHorizontal()){var s={left:w,right:D,top:0,bottom:0};t.update(t.options.fullWidth?m:k,p/2,s)}else t.update(i.minSize.width,_)}function n(t){var i=e.findNextWhere(S,function(e){return e.box===t}),s={left:0,right:0,top:M,bottom:C};i&&t.update(i.minSize.width,_,s)}function r(t){t.isHorizontal()?(t.left=t.options.fullWidth?h:w,t.right=t.options.fullWidth?i-h:w+k,t.top=T,t.bottom=T+t.height,T=t.bottom):(t.left=P,t.right=P+t.width,t.top=M,t.bottom=M+_,P=t.right)}if(t){var h=0,l=0,c=e.where(t.boxes,function(t){return"left"===t.options.position}),d=e.where(t.boxes,function(t){return"right"===t.options.position}),u=e.where(t.boxes,function(t){return"top"===t.options.position}),f=e.where(t.boxes,function(t){return"bottom"===t.options.position}),g=e.where(t.boxes,function(t){return"chartArea"===t.options.position});u.sort(function(t,e){return(e.options.fullWidth?1:0)-(t.options.fullWidth?1:0)}),f.sort(function(t,e){return(t.options.fullWidth?1:0)-(e.options.fullWidth?1:0)});var m=i-2*h,p=s-2*l,b=m/2,v=p/2,x=(i-b)/(c.length+d.length),y=(s-v)/(u.length+f.length),k=m,_=p,S=[];e.each(c.concat(d,u,f),a);var w=h,D=h,M=l,C=l;e.each(c.concat(d),o),e.each(c,function(t){w+=t.width}),e.each(d,function(t){D+=t.width}),e.each(u.concat(f),o),e.each(u,function(t){M+=t.height}),e.each(f,function(t){C+=t.height}),e.each(c.concat(d),n),w=h,D=h,M=l,C=l,e.each(c,function(t){w+=t.width}),e.each(d,function(t){D+=t.width}),e.each(u,function(t){M+=t.height}),e.each(f,function(t){C+=t.height});var A=s-M-C,I=i-w-D;(I!==k||A!==_)&&(e.each(c,function(t){t.height=A}),e.each(d,function(t){t.height=A}),e.each(u,function(t){t.options.fullWidth||(t.width=I)}),e.each(f,function(t){t.options.fullWidth||(t.width=I)}),_=A,k=I);var P=h,T=l;e.each(c.concat(u),r),P+=k,T+=_,e.each(d,r),e.each(f,r),t.chartArea={left:w,top:M,right:w+k,bottom:M+_},e.each(g,function(e){e.left=t.chartArea.left,e.top=t.chartArea.top,e.right=t.chartArea.right,e.bottom=t.chartArea.bottom,e.update(k,_)})}}}}},{}],28:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i=e.noop;t.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(t,e){var i=e.datasetIndex,s=this.chart,a=s.getDatasetMeta(i);a.hidden=null===a.hidden?!s.data.datasets[i].hidden:null,s.update()},labels:{boxWidth:40,padding:10,generateLabels:function(t){var i=t.data;return e.isArray(i.datasets)?i.datasets.map(function(e,i){return{text:e.label,fillStyle:e.backgroundColor,hidden:!t.isDatasetVisible(i),lineCap:e.borderCapStyle,lineDash:e.borderDash,lineDashOffset:e.borderDashOffset,lineJoin:e.borderJoinStyle,lineWidth:e.borderWidth,strokeStyle:e.borderColor,datasetIndex:i}},this):[]}}},t.Legend=t.Element.extend({initialize:function(t){e.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:i,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:i,beforeSetDimensions:i,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:i,beforeBuildLabels:i,buildLabels:function(){this.legendItems=this.options.labels.generateLabels.call(this,this.chart),this.options.reverse&&this.legendItems.reverse()},afterBuildLabels:i,beforeFit:i,fit:function(){var i=this.options,s=i.labels,a=i.display,o=this.ctx,n=t.defaults.global,r=e.getValueOrDefault,h=r(s.fontSize,n.defaultFontSize),l=r(s.fontStyle,n.defaultFontStyle),c=r(s.fontFamily,n.defaultFontFamily),d=e.fontString(h,l,c),u=this.legendHitBoxes=[],f=this.minSize,g=this.isHorizontal();if(g?(f.width=this.maxWidth,f.height=a?10:0):(f.width=a?10:0,f.height=this.maxHeight),a&&g){var m=this.lineWidths=[0],p=this.legendItems.length?h+s.padding:0;o.textAlign="left",o.textBaseline="top",o.font=d,e.each(this.legendItems,function(t,e){var i=s.boxWidth+h/2+o.measureText(t.text).width;m[m.length-1]+i+s.padding>=this.width&&(p+=h+s.padding,m[m.length]=this.left),u[e]={left:0,top:0,width:i,height:h},m[m.length-1]+=i+s.padding},this),f.height+=p}this.width=f.width,this.height=f.height},afterFit:i,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var i=this.options,s=i.labels,a=t.defaults.global,o=a.elements.line,n=this.width,r=this.lineWidths;if(i.display){var h=this.ctx,l={x:this.left+(n-r[0])/2,y:this.top+s.padding,line:0},c=e.getValueOrDefault,d=c(s.fontColor,a.defaultFontColor),u=c(s.fontSize,a.defaultFontSize),f=c(s.fontStyle,a.defaultFontStyle),g=c(s.fontFamily,a.defaultFontFamily),m=e.fontString(u,f,g);if(this.isHorizontal()){h.textAlign="left",h.textBaseline="top",h.lineWidth=.5,h.strokeStyle=d,h.fillStyle=d,h.font=m;var p=s.boxWidth,b=this.legendHitBoxes;e.each(this.legendItems,function(t,e){var i=h.measureText(t.text).width,d=p+u/2+i,f=l.x,g=l.y;f+d>=n&&(g=l.y+=u+s.padding,l.line++,f=l.x=this.left+(n-r[l.line])/2),h.save(),h.fillStyle=c(t.fillStyle,a.defaultColor),h.lineCap=c(t.lineCap,o.borderCapStyle),h.lineDashOffset=c(t.lineDashOffset,o.borderDashOffset),h.lineJoin=c(t.lineJoin,o.borderJoinStyle),h.lineWidth=c(t.lineWidth,o.borderWidth),h.strokeStyle=c(t.strokeStyle,a.defaultColor),h.setLineDash&&h.setLineDash(c(t.lineDash,o.borderDash)),h.strokeRect(f,g,p,u),h.fillRect(f,g,p,u),h.restore(),b[e].left=f,b[e].top=g,h.fillText(t.text,p+u/2+f,g),t.hidden&&(h.beginPath(),h.lineWidth=2,h.moveTo(p+u/2+f,g+u/2),h.lineTo(p+u/2+f+i,g+u/2),h.stroke()),l.x+=d+s.padding},this)}}},handleEvent:function(t){var i=e.getRelativePosition(t,this.chart.chart),s=i.x,a=i.y,o=this.options;if(s>=this.left&&s<=this.right&&a>=this.top&&a<=this.bottom)for(var n=this.legendHitBoxes,r=0;r=h.left&&s<=h.left+h.width&&a>=h.top&&a<=h.top+h.height){o.onClick&&o.onClick.call(this,t,this.legendItems[r]);break}}}})}},{}],29:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.plugins=[],t.pluginService={register:function(e){var i=t.plugins;-1===i.indexOf(e)&&i.push(e)},remove:function(e){var i=t.plugins,s=i.indexOf(e);-1!==s&&i.splice(s,1)},notifyPlugins:function(i,s,a){e.each(t.plugins,function(t){t[i]&&"function"==typeof t[i]&&t[i].apply(a,s)},a)}};var i=e.noop;t.PluginBase=t.Element.extend({beforeInit:i,afterInit:i,beforeUpdate:i,afterUpdate:i,beforeDraw:i,afterDraw:i,destroy:i})}},{}],30:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.scale={display:!0,position:"left",gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:function(t){return""+t}}},t.Scale=t.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(t,i,s){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=i,this.margins=e.extend({left:0,right:0,top:0,bottom:0},s),this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this.beforeBuildTicks(),this.buildTicks(),this.afterBuildTicks(),this.beforeTickToLabelConversion(),this.convertTicksToLabels(),this.afterTickToLabelConversion(),this.beforeCalculateTickRotation(),this.calculateTickRotation(),this.afterCalculateTickRotation(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop,afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){this.ticks=this.ticks.map(function(t,e,i){return this.options.ticks.userCallback?this.options.ticks.userCallback(t,e,i):this.options.ticks.callback(t,e,i)},this)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),s=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),a=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),o=e.fontString(i,s,a);this.ctx.font=o;var n,r=this.ctx.measureText(this.ticks[0]).width,h=this.ctx.measureText(this.ticks[this.ticks.length-1]).width;if(this.labelRotation=this.options.ticks.minRotation||0,this.paddingRight=0,this.paddingLeft=0,this.options.display&&this.isHorizontal()){this.paddingRight=h/2+3,this.paddingLeft=r/2+3,this.longestTextCache||(this.longestTextCache={});for(var l,c,d=e.longestText(this.ctx,o,this.ticks,this.longestTextCache),u=d,f=this.getPixelForTick(1)-this.getPixelForTick(0)-6;u>f&&this.labelRotationthis.yLabelWidth&&(this.paddingLeft=n+i/2),this.paddingRight=i/2,c*d>this.maxHeight){this.labelRotation--;break}this.labelRotation++,u=l*d}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){var i=this.minSize={width:0,height:0},s=this.options,a=s.ticks,o=s.scaleLabel,n=t.defaults.global,r=s.display,h=this.isHorizontal(),l=e.getValueOrDefault(a.fontSize,n.defaultFontSize),c=e.getValueOrDefault(a.fontStyle,n.defaultFontStyle),d=e.getValueOrDefault(a.fontFamily,n.defaultFontFamily),u=e.fontString(l,c,d),f=e.getValueOrDefault(o.fontSize,n.defaultFontSize),g=e.getValueOrDefault(o.fontStyle,n.defaultFontStyle),m=e.getValueOrDefault(o.fontFamily,n.defaultFontFamily),p=(e.fontString(f,g,m),s.gridLines.tickMarkLength);if(h?i.width=this.isFullWidth()?this.maxWidth-this.margins.left-this.margins.right:this.maxWidth:i.width=r?p:0,h?i.height=r?p:0:i.height=this.maxHeight,o.display&&r&&(h?i.height+=1.5*f:i.width+=1.5*f),a.display&&r){this.longestTextCache||(this.longestTextCache={});var b=e.longestText(this.ctx,u,this.ticks,this.longestTextCache);if(h){this.longestLabelWidth=b;var v=Math.sin(e.toRadians(this.labelRotation))*this.longestLabelWidth+1.5*l;i.height=Math.min(this.maxHeight,i.height+v),this.ctx.font=u;var x=this.ctx.measureText(this.ticks[0]).width,y=this.ctx.measureText(this.ticks[this.ticks.length-1]).width,k=Math.cos(e.toRadians(this.labelRotation)),_=Math.sin(e.toRadians(this.labelRotation));this.paddingLeft=0!==this.labelRotation?k*x+3:x/2+3,this.paddingRight=0!==this.labelRotation?_*(l/2)+3:y/2+3}else{var S=this.maxWidth-i.width,w=a.mirror;w?b=0:b+=this.options.ticks.padding,S>b?i.width+=b:i.width=this.maxWidth,this.paddingTop=l/2,this.paddingBottom=l/2}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingTop=Math.max(this.paddingTop-this.margins.top,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0),this.paddingBottom=Math.max(this.paddingBottom-this.margins.bottom,0)),this.width=i.width,this.height=i.height},afterFit:function(){e.callCallback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function i(t){return null===t||"undefined"==typeof t?NaN:"number"==typeof t&&isNaN(t)?NaN:"object"==typeof t?t instanceof Date?t:i(this.isHorizontal()?t.x:t.y):t},getLabelForIndex:e.noop,getPixelForValue:e.noop,getValueForPixel:e.noop,getPixelForTick:function(t,e){if(this.isHorizontal()){var i=this.width-(this.paddingLeft+this.paddingRight),s=i/Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1),a=s*t+this.paddingLeft;e&&(a+=s/2);var o=this.left+Math.round(a);return o+=this.isFullWidth()?this.margins.left:0}var n=this.height-(this.paddingTop+this.paddingBottom);return this.top+t*(n/(this.ticks.length-1))},getPixelForDecimal:function(t){if(this.isHorizontal()){var e=this.width-(this.paddingLeft+this.paddingRight),i=e*t+this.paddingLeft,s=this.left+Math.round(i);return s+=this.isFullWidth()?this.margins.left:0}return this.top+t*this.height},draw:function(i){if(this.options.display){var s,a,o,n,r,h=0!==this.labelRotation,l=this.options.ticks.autoSkip;this.options.ticks.maxTicksLimit&&(r=this.options.ticks.maxTicksLimit);var c=e.getValueOrDefault(this.options.ticks.fontColor,t.defaults.global.defaultFontColor),d=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),u=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),f=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),g=e.fontString(d,u,f),m=this.options.gridLines.tickMarkLength,p=e.getValueOrDefault(this.options.scaleLabel.fontColor,t.defaults.global.defaultFontColor),b=e.getValueOrDefault(this.options.scaleLabel.fontSize,t.defaults.global.defaultFontSize),v=e.getValueOrDefault(this.options.scaleLabel.fontStyle,t.defaults.global.defaultFontStyle),x=e.getValueOrDefault(this.options.scaleLabel.fontFamily,t.defaults.global.defaultFontFamily),y=e.fontString(b,v,x),k=Math.cos(e.toRadians(this.labelRotation)),_=(Math.sin(e.toRadians(this.labelRotation)), +this.longestLabelWidth*k);if(this.ctx.fillStyle=c,this.isHorizontal()){s=!0;var S="bottom"===this.options.position?this.top:this.bottom-m,w="bottom"===this.options.position?this.top+m:this.bottom;if(a=!1,(_/2+this.options.ticks.autoSkipPadding)*this.ticks.length>this.width-(this.paddingLeft+this.paddingRight)&&(a=1+Math.floor((_/2+this.options.ticks.autoSkipPadding)*this.ticks.length/(this.width-(this.paddingLeft+this.paddingRight)))),r&&this.ticks.length>r)for(;!a||this.ticks.length/(a||1)>r;)a||(a=1),a+=1;l||(a=!1),e.each(this.ticks,function(t,o){var n=this.ticks.length===o+1,r=a>1&&o%a>0||o%a===0&&o+a>this.ticks.length;if((!r||n)&&void 0!==t&&null!==t){var l=this.getPixelForTick(o),c=this.getPixelForTick(o,this.options.gridLines.offsetGridLines);this.options.gridLines.display&&(o===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,s=!0):s&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,s=!1),l+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(l,S),this.ctx.lineTo(l,w)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(l,i.top),this.ctx.lineTo(l,i.bottom)),this.ctx.stroke()),this.options.ticks.display&&(this.ctx.save(),this.ctx.translate(c+this.options.ticks.labelOffset,h?this.top+12:"top"===this.options.position?this.bottom-m:this.top+m),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textAlign=h?"right":"center",this.ctx.textBaseline=h?"middle":"top"===this.options.position?"bottom":"top",this.ctx.fillText(t,0,0),this.ctx.restore())}},this),this.options.scaleLabel.display&&(this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillStyle=p,this.ctx.font=y,o=this.left+(this.right-this.left)/2,n="bottom"===this.options.position?this.bottom-b/2:this.top+b/2,this.ctx.fillText(this.options.scaleLabel.labelString,o,n))}else{s=!0;var D="right"===this.options.position?this.left:this.right-5,M="right"===this.options.position?this.left+5:this.right;if(e.each(this.ticks,function(t,a){if(void 0!==t&&null!==t){var o=this.getPixelForTick(a);if(this.options.gridLines.display&&(a===("undefined"!=typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,s=!0):s&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,s=!1),o+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(D,o),this.ctx.lineTo(M,o)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(i.left,o),this.ctx.lineTo(i.right,o)),this.ctx.stroke()),this.options.ticks.display){var n,r=this.getPixelForTick(a,this.options.gridLines.offsetGridLines);this.ctx.save(),"left"===this.options.position?this.options.ticks.mirror?(n=this.right+this.options.ticks.padding,this.ctx.textAlign="left"):(n=this.right-this.options.ticks.padding,this.ctx.textAlign="right"):this.options.ticks.mirror?(n=this.left-this.options.ticks.padding,this.ctx.textAlign="right"):(n=this.left+this.options.ticks.padding,this.ctx.textAlign="left"),this.ctx.translate(n,r+this.options.ticks.labelOffset),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=g,this.ctx.textBaseline="middle",this.ctx.fillText(t,0,0),this.ctx.restore()}}},this),this.options.scaleLabel.display){o="left"===this.options.position?this.left+b/2:this.right-b/2,n=this.top+(this.bottom-this.top)/2;var C="left"===this.options.position?-.5*Math.PI:.5*Math.PI;this.ctx.save(),this.ctx.translate(o,n),this.ctx.rotate(C),this.ctx.textAlign="center",this.ctx.fillStyle=p,this.ctx.font=y,this.ctx.textBaseline="middle",this.ctx.fillText(this.options.scaleLabel.labelString,0,0),this.ctx.restore()}}this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color;var A=this.left,I=this.right,P=this.top,T=this.bottom;this.isHorizontal()?(P=T="top"===this.options.position?this.bottom:this.top,P+=e.aliasPixel(this.ctx.lineWidth),T+=e.aliasPixel(this.ctx.lineWidth)):(A=I="left"===this.options.position?this.right:this.left,A+=e.aliasPixel(this.ctx.lineWidth),I+=e.aliasPixel(this.ctx.lineWidth)),this.ctx.beginPath(),this.ctx.moveTo(A,P),this.ctx.lineTo(I,T),this.ctx.stroke()}}})}},{}],31:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.scaleService={constructors:{},defaults:{},registerScaleType:function(t,i,s){this.constructors[t]=i,this.defaults[t]=e.clone(s)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(i){return this.defaults.hasOwnProperty(i)?e.scaleMerge(t.defaults.scale,this.defaults[i]):{}},updateScaleDefaults:function(t,i){var s=this.defaults;s.hasOwnProperty(t)&&(s[t]=e.extend(s[t],i))},addScalesToLayout:function(i){e.each(i.scales,function(e){t.layoutService.addBox(i,e)})}}}},{}],32:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10,text:""};var i=e.noop;t.Title=t.Element.extend({initialize:function(i){e.extend(this,i),this.options=e.configMerge(t.defaults.global.title,i.options),this.legendHitBoxes=[]},beforeUpdate:i,update:function(t,e,i){return this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this.margins=i,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this.beforeBuildLabels(),this.buildLabels(),this.afterBuildLabels(),this.beforeFit(),this.fit(),this.afterFit(),this.afterUpdate(),this.minSize},afterUpdate:i,beforeSetDimensions:i,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height),this.paddingLeft=0,this.paddingTop=0,this.paddingRight=0,this.paddingBottom=0,this.minSize={width:0,height:0}},afterSetDimensions:i,beforeBuildLabels:i,buildLabels:i,afterBuildLabels:i,beforeFit:i,fit:function(){var i=(this.ctx,e.getValueOrDefault),s=this.options,a=t.defaults.global,o=s.display,n=i(s.fontSize,a.defaultFontSize),r=this.minSize;this.isHorizontal()?(r.width=this.maxWidth,r.height=o?n+2*s.padding:0):(r.width=o?n+2*s.padding:0,r.height=this.maxHeight),this.width=r.width,this.height=r.height},afterFit:i,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var i=this.ctx,s=e.getValueOrDefault,a=this.options,o=t.defaults.global;if(a.display){var n,r,h=s(a.fontSize,o.defaultFontSize),l=s(a.fontStyle,o.defaultFontStyle),c=s(a.fontFamily,o.defaultFontFamily),d=e.fontString(h,l,c),u=0;i.fillStyle=s(a.fontColor,o.defaultFontColor),i.font=d,this.isHorizontal()?(n=this.left+(this.right-this.left)/2,r=this.top+(this.bottom-this.top)/2):(n="left"===a.position?this.left+h/2:this.right-h/2,r=this.top+(this.bottom-this.top)/2,u=Math.PI*("left"===a.position?-.5:.5)),i.save(),i.translate(n,r),i.rotate(u),i.textAlign="center",i.textBaseline="middle",i.fillText(a.text,0,0),i.restore()}}})}},{}],33:[function(t,e,i){"use strict";e.exports=function(t){function e(t,e){return e&&(i.isArray(e)?t=t.concat(e):t.push(e)),t}var i=t.helpers;t.defaults.global.tooltips={enabled:!0,custom:null,mode:"single",backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleColor:"#fff",titleAlign:"left",bodySpacing:2,bodyColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,yAlign:"center",xAlign:"center",caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",callbacks:{beforeTitle:i.noop,title:function(t,e){var i="";return t.length>0&&(t[0].xLabel?i=t[0].xLabel:e.labels.length>0&&t[0].indexthis._chart.height-t.height&&(this._model.yAlign="bottom");var e,i,s,a,o,n=this,r=(this._chartInstance.chartArea.left+this._chartInstance.chartArea.right)/2,h=(this._chartInstance.chartArea.top+this._chartInstance.chartArea.bottom)/2;"center"===this._model.yAlign?(e=function(t){return r>=t},i=function(t){return t>r}):(e=function(e){return e<=t.width/2},i=function(e){return e>=n._chart.width-t.width/2}),s=function(e){return e+t.width>n._chart.width},a=function(e){return e-t.width<0},o=function(t){return h>=t?"top":"bottom"},e(this._model.x)?(this._model.xAlign="left",s(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y))):i(this._model.x)&&(this._model.xAlign="right",a(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=o(this._model.y)))},getBackgroundPoint:function(t,e){var i={x:t.x,y:t.y};return"right"===t.xAlign?i.x-=e.width:"center"===t.xAlign&&(i.x-=e.width/2),"top"===t.yAlign?i.y+=t.caretPadding+t.caretSize:"bottom"===t.yAlign?i.y-=e.height+t.caretPadding+t.caretSize:i.y-=e.height/2,"center"===t.yAlign?"left"===t.xAlign?i.x+=t.caretPadding+t.caretSize:"right"===t.xAlign&&(i.x-=t.caretPadding+t.caretSize):"left"===t.xAlign?i.x-=t.cornerRadius+t.caretPadding:"right"===t.xAlign&&(i.x+=t.cornerRadius+t.caretPadding),i},drawCaret:function(t,e,s,a){var o,n,r,h,l,c,d=this._view,u=this._chart.ctx;"center"===d.yAlign?("left"===d.xAlign?(o=t.x,n=o-d.caretSize,r=o):(o=t.x+e.width,n=o+d.caretSize,r=o),l=t.y+e.height/2,h=l-d.caretSize,c=l+d.caretSize):("left"===d.xAlign?(o=t.x+d.cornerRadius,n=o+d.caretSize,r=n+d.caretSize):"right"===d.xAlign?(o=t.x+e.width-d.cornerRadius,n=o-d.caretSize,r=n-d.caretSize):(n=t.x+e.width/2,o=n-d.caretSize,r=n+d.caretSize),"top"===d.yAlign?(h=t.y,l=h-d.caretSize,c=h):(h=t.y+e.height,l=h+d.caretSize,c=h));var f=i.color(d.backgroundColor);u.fillStyle=f.alpha(s*f.alpha()).rgbString(),u.beginPath(),u.moveTo(o,h),u.lineTo(n,l),u.lineTo(r,c),u.closePath(),u.fill()},drawTitle:function(t,e,s,a){if(e.title.length){s.textAlign=e._titleAlign,s.textBaseline="top";var o=i.color(e.titleColor);s.fillStyle=o.alpha(a*o.alpha()).rgbString(),s.font=i.fontString(e.titleFontSize,e._titleFontStyle,e._titleFontFamily),i.each(e.title,function(i,a){s.fillText(i,t.x,t.y),t.y+=e.titleFontSize+e.titleSpacing,a+1===e.title.length&&(t.y+=e.titleMarginBottom-e.titleSpacing)})}},drawBody:function(t,e,s,a){s.textAlign=e._bodyAlign,s.textBaseline="top";var o=i.color(e.bodyColor);s.fillStyle=o.alpha(a*o.alpha()).rgbString(),s.font=i.fontString(e.bodyFontSize,e._bodyFontStyle,e._bodyFontFamily),i.each(e.beforeBody,function(i){s.fillText(i,t.x,t.y),t.y+=e.bodyFontSize+e.bodySpacing}),i.each(e.body,function(o,n){"single"!==this._options.tooltips.mode&&(s.fillStyle=i.color(e.legendColorBackground).alpha(a).rgbaString(),s.fillRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),s.strokeStyle=i.color(e.labelColors[n].borderColor).alpha(a).rgbaString(),s.strokeRect(t.x,t.y,e.bodyFontSize,e.bodyFontSize),s.fillStyle=i.color(e.labelColors[n].backgroundColor).alpha(a).rgbaString(),s.fillRect(t.x+1,t.y+1,e.bodyFontSize-2,e.bodyFontSize-2),s.fillStyle=i.color(e.bodyColor).alpha(a).rgbaString()),s.fillText(o,t.x+("single"!==this._options.tooltips.mode?e.bodyFontSize+2:0),t.y),t.y+=e.bodyFontSize+e.bodySpacing},this),i.each(e.afterBody,function(i){s.fillText(i,t.x,t.y),t.y+=e.bodyFontSize}),t.y-=e.bodySpacing},drawFooter:function(t,e,s,a){if(e.footer.length){t.y+=e.footerMarginTop,s.textAlign=e._footerAlign,s.textBaseline="top";var o=i.color(e.footerColor);s.fillStyle=o.alpha(a*o.alpha()).rgbString(),s.font=i.fontString(e.footerFontSize,e._footerFontStyle,e._footerFontFamily),i.each(e.footer,function(i){s.fillText(i,t.x,t.y),t.y+=e.footerFontSize+e.footerSpacing})}},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var s=e.caretPadding,a=this.getTooltipSize(e),o={x:e.x,y:e.y},n=Math.abs(e.opacity<.001)?0:e.opacity;if(this._options.tooltips.enabled){var r=i.color(e.backgroundColor);t.fillStyle=r.alpha(n*r.alpha()).rgbString(),i.drawRoundedRectangle(t,o.x,o.y,a.width,a.height,e.cornerRadius),t.fill(),this.drawCaret(o,a,n,s),o.x+=e.xPadding,o.y+=e.yPadding,this.drawTitle(o,e,t,n),this.drawBody(o,e,t,n),this.drawFooter(o,e,t,n)}}}})}},{}],34:[function(t,e,i){"use strict";e.exports=function(t,e){var i=t.helpers,s=t.defaults.global;s.elements.arc={backgroundColor:s.defaultColor,borderColor:"#fff",borderWidth:2},t.elements.Arc=t.Element.extend({inLabelRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)h;)h+=2*Math.PI;for(;o>h;)o-=2*Math.PI;for(;r>o;)o+=2*Math.PI;var l=o>=r&&h>=o,c=n>=s.innerRadius&&n<=s.outerRadius;return l&&c}return!1},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view,i=e.startAngle,s=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,i,s),t.arc(e.x,e.y,e.innerRadius,s,i,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})}},{}],35:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers;t.defaults.global.elements.line={tension:.4,backgroundColor:t.defaults.global.defaultColor,borderWidth:3,borderColor:t.defaults.global.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",fill:!0},t.elements.Line=t.Element.extend({lineToNextPoint:function(t,e,i,s,a){var o=this._chart.ctx;e._view.skip?s.call(this,t,e,i):t._view.skip?a.call(this,t,e,i):0===e._view.tension?o.lineTo(e._view.x,e._view.y):o.bezierCurveTo(t._view.controlPointNextX,t._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y)},draw:function(){function i(t){n._view.skip||r._view.skip?t&&o.lineTo(s._view.scaleZero.x,s._view.scaleZero.y):o.bezierCurveTo(r._view.controlPointNextX,r._view.controlPointNextY,n._view.controlPointPreviousX,n._view.controlPointPreviousY,n._view.x,n._view.y)}var s=this,a=this._view,o=this._chart.ctx,n=this._children[0],r=this._children[this._children.length-1];o.save(),this._children.length>0&&a.fill&&(o.beginPath(),e.each(this._children,function(t,i){var s=e.previousItem(this._children,i),n=e.nextItem(this._children,i);0===i?(this._loop?o.moveTo(a.scaleZero.x,a.scaleZero.y):o.moveTo(t._view.x,a.scaleZero),t._view.skip?this._loop||o.moveTo(n._view.x,this._view.scaleZero):o.lineTo(t._view.x,t._view.y)):this.lineToNextPoint(s,t,n,function(t,e,i){this._loop?o.lineTo(this._view.scaleZero.x,this._view.scaleZero.y):(o.lineTo(t._view.x,this._view.scaleZero),o.moveTo(i._view.x,this._view.scaleZero))},function(t,e){o.lineTo(e._view.x,e._view.y)})},this),this._loop?i(!0):(o.lineTo(this._children[this._children.length-1]._view.x,a.scaleZero),o.lineTo(this._children[0]._view.x,a.scaleZero)),o.fillStyle=a.backgroundColor||t.defaults.global.defaultColor,o.closePath(),o.fill()),o.lineCap=a.borderCapStyle||t.defaults.global.elements.line.borderCapStyle,o.setLineDash&&o.setLineDash(a.borderDash||t.defaults.global.elements.line.borderDash),o.lineDashOffset=a.borderDashOffset||t.defaults.global.elements.line.borderDashOffset,o.lineJoin=a.borderJoinStyle||t.defaults.global.elements.line.borderJoinStyle,o.lineWidth=a.borderWidth||t.defaults.global.elements.line.borderWidth,o.strokeStyle=a.borderColor||t.defaults.global.defaultColor,o.beginPath(),e.each(this._children,function(t,i){var s=e.previousItem(this._children,i),a=e.nextItem(this._children,i);0===i?o.moveTo(t._view.x,t._view.y):this.lineToNextPoint(s,t,a,function(t,e,i){o.moveTo(i._view.x,i._view.y)},function(t,e){o.moveTo(e._view.x,e._view.y)})},this),this._loop&&this._children.length>0&&i(),o.stroke(),o.restore()}})}},{}],36:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i=t.defaults.global;i.elements.point={radius:3,pointStyle:"circle",backgroundColor:i.defaultColor,borderWidth:1,borderColor:i.defaultColor,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},t.elements.Point=t.Element.extend({inRange:function(t,e){var i=this._view;return i?Math.pow(t-i.x,2)+Math.pow(e-i.y,2)0){o.strokeStyle=i.borderColor||t.defaults.global.defaultColor,o.lineWidth=e.getValueOrDefault(i.borderWidth,t.defaults.global.elements.point.borderWidth),o.fillStyle=i.backgroundColor||t.defaults.global.defaultColor;var r,h,l=i.radius;switch(n){default:o.beginPath(),o.arc(s,a,l,0,2*Math.PI),o.closePath(),o.fill();break;case"triangle":o.beginPath();var c=3*l/Math.sqrt(3),d=c*Math.sqrt(3)/2;o.moveTo(s-c/2,a+d/3),o.lineTo(s+c/2,a+d/3),o.lineTo(s,a-2*d/3),o.closePath(),o.fill();break;case"rect":o.fillRect(s-1/Math.SQRT2*l,a-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l),o.strokeRect(s-1/Math.SQRT2*l,a-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l);break;case"rectRot":o.translate(s,a),o.rotate(Math.PI/4),o.fillRect(-1/Math.SQRT2*l,-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l),o.strokeRect(-1/Math.SQRT2*l,-1/Math.SQRT2*l,2/Math.SQRT2*l,2/Math.SQRT2*l),o.setTransform(1,0,0,1,0,0);break;case"cross":o.beginPath(),o.moveTo(s,a+l),o.lineTo(s,a-l),o.moveTo(s-l,a),o.lineTo(s+l,a),o.closePath();break;case"crossRot":o.beginPath(),r=Math.cos(Math.PI/4)*l,h=Math.sin(Math.PI/4)*l,o.moveTo(s-r,a-h),o.lineTo(s+r,a+h),o.moveTo(s-r,a+h),o.lineTo(s+r,a-h),o.closePath();break;case"star":o.beginPath(),o.moveTo(s,a+l),o.lineTo(s,a-l),o.moveTo(s-l,a),o.lineTo(s+l,a),r=Math.cos(Math.PI/4)*l,h=Math.sin(Math.PI/4)*l,o.moveTo(s-r,a-h),o.lineTo(s+r,a+h),o.moveTo(s-r,a+h),o.lineTo(s+r,a-h),o.closePath();break;case"line":o.beginPath(),o.moveTo(s-l,a),o.lineTo(s+l,a),o.closePath();break;case"dash":o.beginPath(),o.moveTo(s,a),o.lineTo(s+l,a),o.closePath()}o.stroke()}}}})}},{}],37:[function(t,e,i){"use strict";e.exports=function(t){var e=(t.helpers,t.defaults.global);e.elements.rectangle={backgroundColor:e.defaultColor,borderWidth:0,borderColor:e.defaultColor,borderSkipped:"bottom"},t.elements.Rectangle=t.Element.extend({draw:function(){function t(t){return h[(c+t)%4]}var e=this._chart.ctx,i=this._view,s=i.width/2,a=i.x-s,o=i.x+s,n=i.base-(i.base-i.y),r=i.borderWidth/2;i.borderWidth&&(a+=r,o-=r,n+=r),e.beginPath(),e.fillStyle=i.backgroundColor,e.strokeStyle=i.borderColor,e.lineWidth=i.borderWidth;var h=[[a,i.base],[a,n],[o,n],[o,i.base]],l=["bottom","left","top","right"],c=l.indexOf(i.borderSkipped,0);-1===c&&(c=0),e.moveTo.apply(e,t(0));for(var d=1;4>d;d++)e.lineTo.apply(e,t(d));e.fill(),i.borderWidth&&e.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view;return i?i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y:!1},inLabelRange:function(t){var e=this._view;return e?t>=e.x-e.width/2&&t<=e.x+e.width/2:!1},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})}},{}],38:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"bottom"},s=t.Scale.extend({determineDataLimits:function(){this.minIndex=0,this.maxIndex=this.chart.data.labels.length-1;var t;void 0!==this.options.ticks.min&&(t=e.indexOf(this.chart.data.labels,this.options.ticks.min),this.minIndex=-1!==t?t:this.minIndex),void 0!==this.options.ticks.max&&(t=e.indexOf(this.chart.data.labels,this.options.ticks.max),this.maxIndex=-1!==t?t:this.maxIndex),this.min=this.chart.data.labels[this.minIndex],this.max=this.chart.data.labels[this.maxIndex]},buildTicks:function(t){this.ticks=0===this.minIndex&&this.maxIndex===this.chart.data.labels.length-1?this.chart.data.labels:this.chart.data.labels.slice(this.minIndex,this.maxIndex+1)},getLabelForIndex:function(t,e){return this.ticks[t]},getPixelForValue:function(t,e,i,s){var a=Math.max(this.maxIndex+1-this.minIndex-(this.options.gridLines.offsetGridLines?0:1),1);if(this.isHorizontal()){var o=this.width-(this.paddingLeft+this.paddingRight),n=o/a,r=n*(e-this.minIndex)+this.paddingLeft;return this.options.gridLines.offsetGridLines&&s&&(r+=n/2),this.left+Math.round(r)}var h=this.height-(this.paddingTop+this.paddingBottom),l=h/a,c=l*(e-this.minIndex)+this.paddingTop;return this.options.gridLines.offsetGridLines&&s&&(c+=l/2),this.top+Math.round(c)},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticks[t],t+this.minIndex,null,e)},getValueForPixel:function(t){var e,i=Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1),s=this.isHorizontal(),a=s?this.width-(this.paddingLeft+this.paddingRight):this.height-(this.paddingTop+this.paddingBottom),o=a/i;return this.options.gridLines.offsetGridLines&&(t-=o/2),t-=s?this.paddingLeft:this.paddingTop,e=0>=t?0:Math.round(t/o)}});t.scaleService.registerScaleType("category",s,i)}},{}],39:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(t,i,s){var a=s.length>3?s[2]-s[1]:s[1]-s[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var o=e.log10(Math.abs(a)),n="";if(0!==t){var r=-1*Math.floor(o);r=Math.max(Math.min(r,20),0),n=t.toFixed(r)}else n="0";return n}}},s=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={},i=!1,s=!1;e.each(this.chart.data.datasets,function(a,o){var n=this.chart.getDatasetMeta(o);void 0===t[n.type]&&(t[n.type]={positiveValues:[],negativeValues:[]});var r=t[n.type].positiveValues,h=t[n.type].negativeValues;this.chart.isDatasetVisible(o)&&(this.isHorizontal()?n.xAxisID===this.id:n.yAxisID===this.id)&&e.each(a.data,function(t,e){var a=+this.getRightValue(t);isNaN(a)||n.data[e].hidden||(r[e]=r[e]||0,h[e]=h[e]||0,this.options.relativePoints?r[e]=100:0>a?(s=!0,h[e]+=a):(i=!0,r[e]+=a))},this)},this),e.each(t,function(t){var i=t.positiveValues.concat(t.negativeValues),s=e.min(i),a=e.max(i);this.min=null===this.min?s:Math.min(this.min,s),this.max=null===this.max?a:Math.max(this.max,a)},this)}else e.each(this.chart.data.datasets,function(t,i){var s=this.chart.getDatasetMeta(i);this.chart.isDatasetVisible(i)&&(this.isHorizontal()?s.xAxisID===this.id:s.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||s.data[e].hidden||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);if(this.options.ticks.beginAtZero){var a=e.sign(this.min),o=e.sign(this.max);0>a&&0>o?this.max=0:a>0&&o>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.max++,this.options.ticks.beginAtZero||this.min--)},buildTicks:function(){this.ticks=[];var i;if(this.isHorizontal())i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.width/50));else{var s=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize);i=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.height/(2*s)))}i=Math.max(2,i);var a,o=this.options.ticks.fixedStepSize&&this.options.ticks.fixedStepSize>0||this.options.ticks.stepSize&&this.options.ticks.stepSize>0;if(o)a=e.getValueOrDefault(this.options.ticks.fixedStepSize,this.options.ticks.stepSize);else{var n=e.niceNum(this.max-this.min,!1);a=e.niceNum(n/(i-1),!0)}var r=Math.floor(this.min/a)*a,h=Math.ceil(this.max/a)*a,l=(h-r)/a;l=e.almostEquals(l,Math.round(l),a/1e3)?Math.round(l):Math.ceil(l),this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:r);for(var c=1;l>c;++c)this.ticks.push(r+c*a);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:h),("left"===this.options.position||"right"===this.options.position)&&this.ticks.reverse(),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},convertTicksToLabels:function(){this.ticksAsNumbers=this.ticks.slice(),this.zeroLineIndex=this.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(this)},getPixelForValue:function(t,e,i,s){var a,o=+this.getRightValue(t),n=this.end-this.start;if(this.isHorizontal()){var r=this.width-(this.paddingLeft+this.paddingRight);return a=this.left+r/n*(o-this.start),Math.round(a+this.paddingLeft)}var h=this.height-(this.paddingTop+this.paddingBottom);return a=this.bottom-this.paddingBottom-h/n*(o-this.start),Math.round(a)},getValueForPixel:function(t){var e;if(this.isHorizontal()){var i=this.width-(this.paddingLeft+this.paddingRight);e=(t-this.left-this.paddingLeft)/i}else{var s=this.height-(this.paddingTop+this.paddingBottom);e=(this.bottom-this.paddingBottom-t)/s}return this.start+(this.end-this.start)*e},getPixelForTick:function(t,e){return this.getPixelForValue(this.ticksAsNumbers[t],null,null,e)}});t.scaleService.registerScaleType("linear",s,i)}},{}],40:[function(t,e,i){"use strict";e.exports=function(t){var e=t.helpers,i={position:"left",ticks:{callback:function(e,i,s){var a=e/Math.pow(10,Math.floor(t.helpers.log10(e))); +return 1===a||2===a||5===a||0===i||i===s.length-1?e.toExponential():""}}},s=t.Scale.extend({determineDataLimits:function(){if(this.min=null,this.max=null,this.options.stacked){var t={};e.each(this.chart.data.datasets,function(i,s){var a=this.chart.getDatasetMeta(s);this.chart.isDatasetVisible(s)&&(this.isHorizontal()?a.xAxisID===this.id:a.yAxisID===this.id)&&(void 0===t[a.type]&&(t[a.type]=[]),e.each(i.data,function(e,i){var s=t[a.type],o=+this.getRightValue(e);isNaN(o)||a.data[i].hidden||(s[i]=s[i]||0,this.options.relativePoints?s[i]=100:s[i]+=o)},this))},this),e.each(t,function(t){var i=e.min(t),s=e.max(t);this.min=null===this.min?i:Math.min(this.min,i),this.max=null===this.max?s:Math.max(this.max,s)},this)}else e.each(this.chart.data.datasets,function(t,i){var s=this.chart.getDatasetMeta(i);this.chart.isDatasetVisible(i)&&(this.isHorizontal()?s.xAxisID===this.id:s.yAxisID===this.id)&&e.each(t.data,function(t,e){var i=+this.getRightValue(t);isNaN(i)||s.data[e].hidden||(null===this.min?this.min=i:ithis.max&&(this.max=i))},this)},this);this.min=void 0!==this.options.ticks.min?this.options.ticks.min:this.min,this.max=void 0!==this.options.ticks.max?this.options.ticks.max:this.max,this.min===this.max&&(0!==this.min&&null!==this.min?(this.min=Math.pow(10,Math.floor(e.log10(this.min))-1),this.max=Math.pow(10,Math.floor(e.log10(this.max))+1)):(this.min=1,this.max=10))},buildTicks:function(){this.ticks=[];for(var t=void 0!==this.options.ticks.min?this.options.ticks.min:Math.pow(10,Math.floor(e.log10(this.min)));tthis.max&&(this.max=i))},this)}},this),this.options.ticks.beginAtZero){var t=e.sign(this.min),i=e.sign(this.max);0>t&&0>i?this.max=0:t>0&&i>0&&(this.min=0)}void 0!==this.options.ticks.min?this.min=this.options.ticks.min:void 0!==this.options.ticks.suggestedMin&&(this.min=Math.min(this.min,this.options.ticks.suggestedMin)),void 0!==this.options.ticks.max?this.max=this.options.ticks.max:void 0!==this.options.ticks.suggestedMax&&(this.max=Math.max(this.max,this.options.ticks.suggestedMax)),this.min===this.max&&(this.min--,this.max++)},buildTicks:function(){this.ticks=[];var i=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),s=Math.min(this.options.ticks.maxTicksLimit?this.options.ticks.maxTicksLimit:11,Math.ceil(this.drawingArea/(1.5*i)));s=Math.max(2,s);var a=e.niceNum(this.max-this.min,!1),o=e.niceNum(a/(s-1),!0),n=Math.floor(this.min/o)*o,r=Math.ceil(this.max/o)*o,h=Math.ceil((r-n)/o);this.ticks.push(void 0!==this.options.ticks.min?this.options.ticks.min:n);for(var l=1;h>l;++l)this.ticks.push(n+l*o);this.ticks.push(void 0!==this.options.ticks.max?this.options.ticks.max:r),this.max=e.max(this.ticks),this.min=e.min(this.ticks),this.options.ticks.reverse?(this.ticks.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),this.zeroLineIndex=this.ticks.indexOf(0)},convertTicksToLabels:function(){t.Scale.prototype.convertTicksToLabels.call(this),this.pointLabels=this.chart.data.labels.map(this.options.pointLabels.callback,this)},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var i,s,a,o,n,r,h,l,c,d,u,f,g=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),m=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),p=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),b=e.fontString(g,m,p),v=e.min([this.height/2-g-5,this.width/2]),x=this.width,y=0;for(this.ctx.font=b,s=0;sx&&(x=i.x+o,n=s),i.x-ox&&(x=i.x+a,n=s):s>this.getValueCount()/2&&i.x-a0||this.options.reverse){var o=this.getDistanceFromCenterForValue(this.ticks[a]),n=this.yCenter-o;if(this.options.gridLines.display)if(i.strokeStyle=this.options.gridLines.color,i.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)i.beginPath(),i.arc(this.xCenter,this.yCenter,o,0,2*Math.PI),i.closePath(),i.stroke();else{i.beginPath();for(var r=0;r=0;s--){if(this.options.angleLines.display){var a=this.getPointPosition(s,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max));i.beginPath(),i.moveTo(this.xCenter,this.yCenter),i.lineTo(a.x,a.y),i.stroke(),i.closePath()}var o=this.getPointPosition(s,this.getDistanceFromCenterForValue(this.options.reverse?this.min:this.max)+5),n=e.getValueOrDefault(this.options.pointLabels.fontColor,t.defaults.global.defaultFontColor),r=e.getValueOrDefault(this.options.pointLabels.fontSize,t.defaults.global.defaultFontSize),h=e.getValueOrDefault(this.options.pointLabels.fontStyle,t.defaults.global.defaultFontStyle),l=e.getValueOrDefault(this.options.pointLabels.fontFamily,t.defaults.global.defaultFontFamily),c=e.fontString(r,h,l);i.font=c,i.fillStyle=n;var d=this.pointLabels.length,u=this.pointLabels.length/2,f=u/2,g=f>s||s>d-f,m=s===f||s===d-f;0===s?i.textAlign="center":s===u?i.textAlign="center":u>s?i.textAlign="left":i.textAlign="right",m?i.textBaseline="middle":g?i.textBaseline="bottom":i.textBaseline="top",i.fillText(this.pointLabels[s]?this.pointLabels[s]:"",o.x,o.y)}}}}});t.scaleService.registerScaleType("radialLinear",s,i)}},{}],42:[function(t,e,i){"use strict";var s=t("moment");s="function"==typeof s?s:window.moment,e.exports=function(t){var e=t.helpers,i={units:[{name:"millisecond",steps:[1,2,5,10,20,50,100,250,500]},{name:"second",steps:[1,2,5,10,30]},{name:"minute",steps:[1,2,5,10,30]},{name:"hour",steps:[1,2,3,6,12]},{name:"day",steps:[1,2,5]},{name:"week",maxStep:4},{name:"month",maxStep:3},{name:"quarter",maxStep:4},{name:"year",maxStep:!1}]},a={position:"bottom",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm:ss a",hour:"MMM D, hA",day:"ll",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1}},o=t.Scale.extend({initialize:function(){if(!s)throw new Error("Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com");t.Scale.prototype.initialize.call(this)},getLabelMoment:function(t,e){return this.labelMoments[t][e]},getMomentStartOf:function(t){return"week"===this.options.time.unit&&this.options.time.isoWeekday!==!1?t.clone().startOf("isoWeek").isoWeekday(this.options.time.isoWeekday):t.clone().startOf(this.tickUnit)},determineDataLimits:function(){this.labelMoments=[];var t=[];this.chart.data.labels&&this.chart.data.labels.length>0?(e.each(this.chart.data.labels,function(e,i){var s=this.parseTime(e);s.isValid()&&(this.options.time.round&&s.startOf(this.options.time.round),t.push(s))},this),this.firstTick=s.min.call(this,t),this.lastTick=s.max.call(this,t)):(this.firstTick=null,this.lastTick=null),e.each(this.chart.data.datasets,function(i,a){var o=[],n=this.chart.isDatasetVisible(a);"object"==typeof i.data[0]?e.each(i.data,function(t,e){var i=this.parseTime(this.getRightValue(t));i.isValid()&&(this.options.time.round&&i.startOf(this.options.time.round),o.push(i),n&&(this.firstTick=null!==this.firstTick?s.min(this.firstTick,i):i,this.lastTick=null!==this.lastTick?s.max(this.lastTick,i):i))},this):o=t,this.labelMoments.push(o)},this),this.options.time.min&&(this.firstTick=this.parseTime(this.options.time.min)),this.options.time.max&&(this.lastTick=this.parseTime(this.options.time.max)),this.firstTick=(this.firstTick||s()).clone(),this.lastTick=(this.lastTick||s()).clone()},buildTicks:function(s){this.ctx.save();var a=e.getValueOrDefault(this.options.ticks.fontSize,t.defaults.global.defaultFontSize),o=e.getValueOrDefault(this.options.ticks.fontStyle,t.defaults.global.defaultFontStyle),n=e.getValueOrDefault(this.options.ticks.fontFamily,t.defaults.global.defaultFontFamily),r=e.fontString(a,o,n);if(this.ctx.font=r,this.ticks=[],this.unitScale=1,this.scaleSizeInUnits=0,this.options.time.unit)this.tickUnit=this.options.time.unit||"day",this.displayFormat=this.options.time.displayFormats[this.tickUnit],this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,1);else{var h=this.isHorizontal()?this.width-(this.paddingLeft+this.paddingRight):this.height-(this.paddingTop+this.paddingBottom),l=this.tickFormatFunction(this.firstTick,0,[]),c=this.ctx.measureText(l).width,d=Math.cos(e.toRadians(this.options.ticks.maxRotation)),u=Math.sin(e.toRadians(this.options.ticks.maxRotation));c=c*d+a*u;var f=h/c;this.tickUnit="millisecond",this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0),this.displayFormat=this.options.time.displayFormats[this.tickUnit];for(var g=0,m=i.units[g];g=Math.ceil(this.scaleSizeInUnits/f)){this.unitScale=e.getValueOrDefault(this.options.time.unitStepSize,m.steps[p]);break}break}if(m.maxStep===!1||Math.ceil(this.scaleSizeInUnits/f)=0)break;k%this.unitScale===0&&this.ticks.push(_)}var S=this.ticks[this.ticks.length-1].diff(this.lastTick,this.tickUnit);(0!==S||0===this.scaleSizeInUnits)&&(this.options.time.max?(this.ticks.push(this.lastTick.clone()),this.scaleSizeInUnits=this.lastTick.diff(this.ticks[0],this.tickUnit,!0)):(this.ticks.push(this.lastTick.clone()),this.scaleSizeInUnits=this.lastTick.diff(this.firstTick,this.tickUnit,!0))),this.ctx.restore()},getLabelForIndex:function(t,e){var i=this.chart.data.labels&&t Date: Sat, 14 May 2016 21:55:43 +0200 Subject: [PATCH 122/206] Remove references to unused library. --- app/Helpers/Report/ReportHelper.php | 6 +----- app/Providers/FireflyServiceProvider.php | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index 98e3c3fa14..9117ef8216 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -34,8 +34,6 @@ class ReportHelper implements ReportHelperInterface /** @var BudgetRepositoryInterface */ protected $budgetRepository; - /** @var ReportQueryInterface */ - protected $query; /** @var TagRepositoryInterface */ protected $tagRepository; @@ -43,13 +41,11 @@ class ReportHelper implements ReportHelperInterface * ReportHelper constructor. * * - * @param ReportQueryInterface $query * @param BudgetRepositoryInterface $budgetRepository * @param TagRepositoryInterface $tagRepository */ - public function __construct(ReportQueryInterface $query, BudgetRepositoryInterface $budgetRepository, TagRepositoryInterface $tagRepository) + public function __construct(BudgetRepositoryInterface $budgetRepository, TagRepositoryInterface $tagRepository) { - $this->query = $query; $this->budgetRepository = $budgetRepository; $this->tagRepository = $tagRepository; } diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index bcf28fdce8..6fce634483 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -95,7 +95,6 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind('FireflyIII\Generator\Chart\Report\ReportChartGeneratorInterface', 'FireflyIII\Generator\Chart\Report\ChartJsReportChartGenerator'); $this->app->bind('FireflyIII\Helpers\Help\HelpInterface', 'FireflyIII\Helpers\Help\Help'); $this->app->bind('FireflyIII\Helpers\Report\ReportHelperInterface', 'FireflyIII\Helpers\Report\ReportHelper'); - $this->app->bind('FireflyIII\Helpers\Report\ReportQueryInterface', 'FireflyIII\Helpers\Report\ReportQuery'); $this->app->bind('FireflyIII\Helpers\FiscalHelperInterface', 'FireflyIII\Helpers\FiscalHelper'); $this->app->bind('FireflyIII\Helpers\Report\AccountReportHelperInterface', 'FireflyIII\Helpers\Report\AccountReportHelper'); $this->app->bind('FireflyIII\Helpers\Report\BalanceReportHelperInterface', 'FireflyIII\Helpers\Report\BalanceReportHelper'); From e64b40d58bd84aa4daf056e737a8bfeab1346dcf Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 22:11:49 +0200 Subject: [PATCH 123/206] Experimental query. [skip ci] --- .../Account/AccountRepository.php | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 169b3fcae1..3ac9824495 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -13,6 +13,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; use Log; @@ -323,8 +324,25 @@ class AccountRepository implements AccountRepositoryInterface } if ($accounts->count() > 0) { $accountIds = $accounts->pluck('id')->toArray(); - $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); - $query->whereIn('t.account_id', $accountIds); + $query->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + } + ); + $query->leftJoin( + 'transactions as destination', function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '>', 0); + } + ); + + // XOR. must be either. + $query->where( + function (Builder $query) use ($accountIds) { + $query->whereIn('source.account_id', $accountIds, 'xor') + ->whereIn('destination.account_id', $accountIds, 'xor'); + } + ); + } // that should do it: $complete = $query->get(TransactionJournal::queryFields()); From b0bb7903863a1e228f02a144b7413dbe102be1e3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 22:12:16 +0200 Subject: [PATCH 124/206] Experimental query. [skip ci] --- app/Repositories/Account/AccountRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 3ac9824495..feb4770331 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -13,7 +13,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Query\Builder; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; use Log; From 29be16dcbaf93c9bb6631cb71aa67850b51b1c6b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 22:21:08 +0200 Subject: [PATCH 125/206] Fix complex query. [skip ci] --- app/Repositories/Account/AccountRepository.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index feb4770331..40e6f71bf1 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -13,7 +13,6 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\User; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; use Log; @@ -326,22 +325,16 @@ class AccountRepository implements AccountRepositoryInterface $accountIds = $accounts->pluck('id')->toArray(); $query->leftJoin( 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0); + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); } ); $query->leftJoin( 'transactions as destination', function (JoinClause $join) { - $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '>', 0); + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); } ); - - // XOR. must be either. - $query->where( - function (Builder $query) use ($accountIds) { - $query->whereIn('source.account_id', $accountIds, 'xor') - ->whereIn('destination.account_id', $accountIds, 'xor'); - } - ); + $set = join(', ', $accountIds); + $query->whereRaw('(source.account_id in (' . $set . ') XOR destination.account_id in (' . $set . '))'); } // that should do it: From 0d39161ec35edd012c5d5e101daa189beb874dda Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 22:23:55 +0200 Subject: [PATCH 126/206] Fix iban in test data. --- app/Support/Migration/TestData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index ee367c81d5..2839a07636 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -73,7 +73,7 @@ class TestData 'active' => 1, 'encrypted' => 1, 'virtual_balance' => 0, - 'iban' => $account['iban'] ?? null, + 'iban' => isset($account['iban']) ? Crypt::encrypt($account['iban']) : null, ]; } DB::table('accounts')->insert($insert); From 446ab62d38e4b985cbd80e2c0fad9edadfc666a1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 May 2016 23:14:49 +0200 Subject: [PATCH 127/206] View updates. [skip ci] --- resources/views/popup/list/journals.twig | 18 +++++++----------- resources/views/popup/report/income-entry.twig | 1 + 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/resources/views/popup/list/journals.twig b/resources/views/popup/list/journals.twig index f4bf459842..a75d9e445e 100644 --- a/resources/views/popup/list/journals.twig +++ b/resources/views/popup/list/journals.twig @@ -39,27 +39,23 @@ {{ journal.description }} - {{ journal|formatJournal }} + {% if not accountPerspective %} + {{ journal|formatJournal }} + {% else %} + {{ formatPerspective(journal, accountPerspective)|raw }} + {% endif %} {{ journal.date.formatLocalized(monthAndDayFormat) }} {% if not hideSource %} - {% if journal.source_account_type == 'Cash account' %} - (cash) - {% else %} - {{ journal.source_account_name }} - {% endif %} + {{ sourceAccount(journal)|raw }} {% endif %} {% if not hideDestination %} - {% if journal.destination_account_type == 'Cash account' %} - (cash) - {% else %} - {{ journal.destination_account_name }} - {% endif %} + {{ destinationAccount(journal)|raw }} {% endif %} diff --git a/resources/views/popup/report/income-entry.twig b/resources/views/popup/report/income-entry.twig index 504960ea69..71ffb20f65 100644 --- a/resources/views/popup/report/income-entry.twig +++ b/resources/views/popup/report/income-entry.twig @@ -8,6 +8,7 @@ {% endfor %} @@ -249,7 +245,7 @@
- + {% if transactions.count > 2 %}
@@ -270,20 +266,24 @@ - {% for t in transactions %} + {% for index, t in transactions %} - {{ t.description }} + + {% if (index+1) != transactions|length %} + {{ t.description }} + {% endif %} + {{ t.account.name }} ({{ t.account.accounttype.type|_ }}) {{ t.sum|formatAmount }} {{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }} - {% if t.budgets[0] %} - {{ t.budgets[0].name }} + {% if (index+1) != transactions|length %} + {{ transactionBudgets(t)|raw }} {% endif %} - {% if t.categories[0] %} - {{ t.categories[0].name }} + {% if (index+1) != transactions|length %} + {{ transactionCategories(t)|raw }} {% endif %} diff --git a/storage/database/seed.local.json b/storage/database/seed.local.json index 7fd523c7ed..68bdbf2164 100644 --- a/storage/database/seed.local.json +++ b/storage/database/seed.local.json @@ -293,6 +293,18 @@ { "name": "Going out", "user_id": 1 + }, + { + "name": "Multi budget A", + "user_id": 1 + }, + { + "name": "Multi budget B", + "user_id": 1 + }, + { + "name": "Multi budget C", + "user_id": 1 } ], "budget-limits": [ @@ -385,6 +397,18 @@ { "name": "Going out", "user_id": 1 + }, + { + "name": "Multi category A", + "user_id": 1 + }, + { + "name": "Multi category B", + "user_id": 1 + }, + { + "name": "Multi category C", + "user_id": 1 } ], "piggy-banks": [ @@ -840,5 +864,59 @@ "mime": "text\/plain", "uploaded": 1 } - ] + ], + "multi-withdrawals": [ + { + "user_id": 1, + "date": "2016-03-12", + "description": "Even multi-withdrawal (50, 50)", + "destination_ids": [ + 18, + 19 + ], + "source_id": 1, + "amounts": [ + 50, + 50 + ], + "category_ids": [ + 7, + 8, + 9 + ], + "budget_ids": [ + 7, + 8, + 9 + ] + }, + { + "user_id": 1, + "date": "2016-05-12", + "description": "Uneven multi-withdrawal (15,34,51)", + "destination_ids": [ + 18, + 19, + 20 + ], + "source_id": 1, + "amounts": [ + 14, + 35, + 51 + ], + "category_ids": [ + 7, + 8, + 9 + ], + "budget_ids": [ + 7, + 8, + 9 + ] + } + ], + "multi-deposits": [], + "multi-transfers": [] } \ No newline at end of file From 1c93d8bf798682e6e8670558ba28b19f8bdf2ecf Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 12:08:41 +0200 Subject: [PATCH 129/206] More test data and better views. --- .../Transaction/SplitController.php | 254 ++++++------------ .../Controllers/TransactionController.php | 204 +++++--------- app/Http/Requests/SplitJournalFormRequest.php | 4 +- app/Http/routes.php | 9 +- .../Journal/JournalRepository.php | 117 ++++++++ .../Journal/JournalRepositoryInterface.php | 42 ++- .../PiggyBank/PiggyBankRepository.php | 17 +- .../PiggyBankRepositoryInterface.php | 9 +- app/Support/Binder/UnfinishedJournal.php | 55 ++++ app/Support/Migration/TestData.php | 68 +++++ config/firefly.php | 39 +-- resources/lang/en_US/form.php | 252 ++++++++--------- .../journals/{from-store.twig => create.twig} | 112 ++++---- resources/views/split/journals/edit.twig | 24 +- resources/views/split/journals/show.twig | 206 ++++++++++++++ resources/views/transactions/show.twig | 97 +------ storage/database/seed.local.json | 43 ++- 17 files changed, 926 insertions(+), 626 deletions(-) create mode 100644 app/Support/Binder/UnfinishedJournal.php rename resources/views/split/journals/{from-store.twig => create.twig} (74%) create mode 100644 resources/views/split/journals/show.twig diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index f370205019..d942b44664 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -13,17 +13,12 @@ namespace FireflyIII\Http\Controllers\Transaction; use ExpandedForm; use FireflyIII\Crud\Split\JournalInterface; use FireflyIII\Events\TransactionJournalUpdated; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SplitJournalFormRequest; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Http\Request; -use Input; use Log; use Preferences; use Session; @@ -48,6 +43,37 @@ class SplitController extends Controller View::share('title', trans('firefly.split-transactions')); } + /** + * @param TransactionJournal $journal + * + * @return View + */ + public function create(TransactionJournal $journal) + { + $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); + $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); + $sessionData = session('journal-data', []); + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); + $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $preFilled = [ + 'what' => $sessionData['what'] ?? 'withdrawal', + 'journal_amount' => $sessionData['amount'] ?? 0, + 'journal_source_account_id' => $sessionData['source_account_id'] ?? 0, + 'journal_source_account_name' => $sessionData['source_account_name'] ?? '', + 'description' => [$journal->description], + 'destination_account_name' => [$sessionData['destination_account_name']], + 'destination_account_id' => [$sessionData['destination_account_id']], + 'amount' => [$sessionData['amount']], + 'budget_id' => [$sessionData['budget_id']], + 'category' => [$sessionData['category']], + ]; + + return view('split.journals.create', compact('journal', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize')); + } + /** * @param Request $request * @param TransactionJournal $journal @@ -56,24 +82,14 @@ class SplitController extends Controller */ public function edit(Request $request, TransactionJournal $journal) { - $count = $journal->transactions()->count(); - if ($count === 2) { - return redirect(route('transactions.edit', [$journal->id])); - } - - /** @var CurrencyRepositoryInterface $currencyRepository */ - $currencyRepository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - - /** @var BudgetRepositoryInterface $budgetRepository */ - $budgetRepository = app(BudgetRepositoryInterface::class); - - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); - $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $preFilled = $this->arrayFromJournal($request, $journal); + $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); + $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $preFilled = $this->arrayFromJournal($request, $journal); Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']); @@ -91,62 +107,35 @@ class SplitController extends Controller } /** - * @param Request $request - * - * @return mixed - * @throws FireflyException - */ - public function journalFromStore(Request $request) - { - if ($request->old('journal_currency_id')) { - $preFilled = $this->arrayFromOldData($request->old()); - } else { - $preFilled = $this->arrayFromSession(); - } - - Session::flash('preFilled', $preFilled); - View::share('subTitle', trans('firefly.split-new-transaction')); - - /** @var CurrencyRepositoryInterface $currencyRepository */ - $currencyRepository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - - /** @var BudgetRepositoryInterface $budgetRepository */ - $budgetRepository = app(BudgetRepositoryInterface::class); - - $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); - $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - - return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets', 'preFilled')); - - - } - - /** - * @param SplitJournalFormRequest $request * @param JournalInterface $repository + * @param SplitJournalFormRequest $request + * @param TransactionJournal $journal * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws FireflyException */ - public function postJournalFromStore(SplitJournalFormRequest $request, JournalInterface $repository) + public function store(JournalInterface $repository, SplitJournalFormRequest $request, TransactionJournal $journal) { $data = $request->getSplitData(); - // store an empty journal first. This will be the place holder. - $journal = $repository->storeJournal($data); - // Then, store each transaction individually. - - if (is_null($journal->id)) { - throw new FireflyException('Could not store transaction.'); + foreach ($data['transactions'] as $transaction) { + $repository->storeTransaction($journal, $transaction); } - // forget temp journal data - Session::forget('temporary_split_data'); + // TODO move to repository + $journal->completed = 1; + $journal->save(); - // this is where we originally came from. + Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); + Preferences::mark(); + + if (intval($request->get('create_another')) === 1) { + // set value so create routine will not overwrite URL: + Session::put('transactions.create.fromStore', true); + + return redirect(route('transactions.create', [$request->input('what')]))->withInput(); + } + + // redirect to previous URL. return redirect(session('transactions.create.url')); } @@ -180,7 +169,7 @@ class SplitController extends Controller Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])]))); Preferences::mark(); - if (intval(Input::get('return_to_edit')) === 1) { + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('transactions.edit-split.fromUpdate', true); @@ -190,9 +179,6 @@ class SplitController extends Controller // redirect to previous URL. return redirect(session('transactions.edit-split.url')); - - // update all: - } /** @@ -206,27 +192,32 @@ class SplitController extends Controller if (Session::has('_old_input')) { Log::debug('Old input: ', session('_old_input')); } - $sourceAccounts = TransactionJournal::sourceAccountList($journal); - $firstSourceId = $sourceAccounts->first()->id; - $array = [ - 'journal_description' => $request->old('journal_description', $journal->description), - 'journal_amount' => TransactionJournal::amountPositive($journal), - 'sourceAccounts' => $sourceAccounts, - 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id), - 'destinationAccounts' => TransactionJournal::destinationAccountList($journal), - 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), - 'date' => $request->old('date', $journal->date), - 'interest_date' => $request->old('interest_date', $journal->interest_date), - 'book_date' => $request->old('book_date', $journal->book_date), - 'process_date' => $request->old('process_date', $journal->process_date), - 'description' => [], - 'destination_account_id' => [], - 'destination_account_name' => [], - 'amount' => [], - 'budget_id' => [], - 'category' => [], + $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $array = [ + 'journal_description' => $request->old('journal_description', $journal->description), + 'journal_amount' => TransactionJournal::amountPositive($journal), + 'sourceAccounts' => $sourceAccounts, + 'journal_source_account_id' => $sourceAccounts->first()->id, + 'journal_source_account_name' => $sourceAccounts->first()->name, + 'journal_destination_account_id' => $destinationAccounts->first()->id, + 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id), + 'destinationAccounts' => $destinationAccounts, + 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), + 'date' => $request->old('date', $journal->date), + 'interest_date' => $request->old('interest_date', $journal->interest_date), + 'book_date' => $request->old('book_date', $journal->book_date), + 'process_date' => $request->old('process_date', $journal->process_date), + 'description' => [], + 'source_account_id' => [], + 'source_account_name' => [], + 'destination_account_id' => [], + 'destination_account_name' => [], + 'amount' => [], + 'budget_id' => [], + 'category' => [], ]; - $index = 0; + $index = 0; /** @var Transaction $transaction */ foreach ($journal->transactions()->get() as $transaction) { $budget = $transaction->budgets()->first(); @@ -245,10 +236,10 @@ class SplitController extends Controller $categoryName = $request->old('category')[$index] ?? $categoryName; $amount = $request->old('amount')[$index] ?? $transaction->amount; $description = $request->old('description')[$index] ?? $transaction->description; - $destinationName = $request->old('destination_account_name')[$index] ??$transaction->account->name; + $destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name; - - if ($journal->isWithdrawal() && $transaction->account_id !== $firstSourceId) { + // any transfer not from the source: + if (($journal->isWithdrawal() || $journal->isDeposit()) && $transaction->account_id !== $sourceAccounts->first()->id) { $array['description'][] = $description; $array['destination_account_id'][] = $transaction->account_id; $array['destination_account_name'][] = $destinationName; @@ -258,79 +249,10 @@ class SplitController extends Controller // only add one when "valid" transaction $index++; } - - } + return $array; } - /** - * @param array $old - * - * @return array - */ - private function arrayFromOldData(array $old): array - { - // this array is pretty much equal to what we expect it to be. - Log::debug('Prefilled', $old); - - return $old; - } - - /** - * @return array - * @throws FireflyException - */ - private function arrayFromSession(): array - { - // expect data to be in session or in post? - $data = session('temporary_split_data'); - - if (!is_array($data)) { - Log::error('Could not find transaction data in your session. Please go back and try again.', ['data' => $data]); // translate me. - throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me. - } - - Log::debug('Journal data', $data); - - $preFilled = [ - 'what' => $data['what'], - 'journal_description' => $data['description'], - 'journal_source_account_id' => $data['source_account_id'], - 'journal_source_account_name' => $data['source_account_name'], - 'journal_destination_account_id' => $data['destination_account_id'], - 'journal_destination_account_name' => $data['destination_account_name'], - 'journal_amount' => $data['amount'], - 'journal_currency_id' => $data['amount_currency_id_amount'], - 'date' => $data['date'], - 'interest_date' => $data['interest_date'], - 'book_date' => $data['book_date'], - 'process_date' => $data['process_date'], - - 'description' => [], - 'destination_account_id' => [], - 'destination_account_name' => [], - 'amount' => [], - 'budget_id' => [], - 'category' => [], - ]; - - // create the first transaction: - $preFilled['description'][] = $data['description']; - $preFilled['destination_account_id'][] = $data['destination_account_id']; - $preFilled['destination_account_name'][] = $data['destination_account_name']; - $preFilled['amount'][] = $data['amount']; - $preFilled['budget_id'][] = $data['budget_id']; - $preFilled['category'][] = $data['category']; - - // echo '
';
-        //        var_dump($data);
-        //        var_dump($preFilled);
-        //        exit;
-        Log::debug('Prefilled', $preFilled);
-
-        return $preFilled;
-    }
-
 }
\ No newline at end of file
diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php
index 2115d548c1..ec0a137efb 100644
--- a/app/Http/Controllers/TransactionController.php
+++ b/app/Http/Controllers/TransactionController.php
@@ -9,10 +9,8 @@
 
 namespace FireflyIII\Http\Controllers;
 
-use Amount;
 use Auth;
 use Carbon\Carbon;
-use DB;
 use ExpandedForm;
 use FireflyIII\Events\TransactionJournalStored;
 use FireflyIII\Events\TransactionJournalUpdated;
@@ -21,17 +19,12 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
 use FireflyIII\Http\Requests\JournalFormRequest;
 use FireflyIII\Http\Requests\MassDeleteJournalRequest;
 use FireflyIII\Http\Requests\MassEditJournalRequest;
-use FireflyIII\Models\PiggyBank;
-use FireflyIII\Models\PiggyBankEvent;
-use FireflyIII\Models\Transaction;
 use FireflyIII\Models\TransactionJournal;
 use FireflyIII\Models\TransactionType;
 use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
-use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
 use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
-use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
+use Illuminate\Http\Request;
 use Illuminate\Support\Collection;
-use Input;
 use Log;
 use Preferences;
 use Response;
@@ -65,31 +58,24 @@ class TransactionController extends Controller
      */
     public function create(ARI $repository, string $what = TransactionType::DEPOSIT)
     {
-        /** @var BudgetRepositoryInterface $budgetRepository */
-        $budgetRepository = app(BudgetRepositoryInterface::class);
-
-        /** @var PiggyBankRepositoryInterface $piggyRepository */
-        $piggyRepository = app(PiggyBankRepositoryInterface::class);
-
-        $what          = strtolower($what);
-        $uploadSize    = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
-        $assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account']));
-        $budgets       = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
-        $piggyBanks    = $piggyRepository->getPiggyBanks();
-        /** @var PiggyBank $piggy */
-        foreach ($piggyBanks as $piggy) {
-            $piggy->name = $piggy->name . ' (' . Amount::format($piggy->currentRelevantRep()->currentamount, false) . ')';
-        }
-
-        $piggies   = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
-        $preFilled = Session::has('preFilled') ? session('preFilled') : [];
-        $subTitle  = trans('form.add_new_' . $what);
+        $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+        $piggyRepository  = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
+        $what             = strtolower($what);
+        $uploadSize       = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
+        $assetAccounts    = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account']));
+        $budgets          = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+        $piggyBanks       = $piggyRepository->getPiggyBanksWithAmount();
+        $piggies          = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
+        $preFilled        = Session::has('preFilled') ? session('preFilled') : [];
+        $subTitle         = trans('form.add_new_' . $what);
 
         Session::put('preFilled', $preFilled);
 
         // put previous url in session if not redirect from store (not "create another").
         if (session('transactions.create.fromStore') !== true) {
-            Session::put('transactions.create.url', URL::previous());
+            $url = URL::previous();
+            Log::debug('TransactionController::create. Previous URL is ' . $url);
+            Session::put('transactions.create.url', $url);
         }
         Session::forget('transactions.create.fromStore');
         Session::flash('gaEventCategory', 'transactions');
@@ -131,7 +117,7 @@ class TransactionController extends Controller
      */
     public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
     {
-        $type = strtolower($transactionJournal->transaction_type_type ?? TransactionJournal::transactionTypeStr($transactionJournal));
+        $type = TransactionJournal::transactionTypeStr($transactionJournal);
         Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)])));
 
         $repository->delete($transactionJournal);
@@ -153,24 +139,18 @@ class TransactionController extends Controller
         if ($count > 2) {
             return redirect(route('split.journal.edit', [$journal->id]));
         }
-        /** @var ARI $accountRepository */
-        $accountRepository = app(ARI::class);
-        /** @var BudgetRepositoryInterface $budgetRepository */
-        $budgetRepository = app(BudgetRepositoryInterface::class);
-        /** @var PiggyBankRepositoryInterface $piggyRepository */
-        $piggyRepository = app(PiggyBankRepositoryInterface::class);
-
-        $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
-        $budgetList    = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
-        $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks());
-        $maxFileSize   = Steam::phpBytes(ini_get('upload_max_filesize'));
-        $maxPostSize   = Steam::phpBytes(ini_get('post_max_size'));
-        $uploadSize    = min($maxFileSize, $maxPostSize);
-        $what          = strtolower(TransactionJournal::transactionTypeStr($journal));
-        $subTitle      = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
-
-
-        $preFilled = [
+        $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
+        $budgetRepository  = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
+        $piggyRepository   = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
+        $assetAccounts     = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
+        $budgetList        = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
+        $piggyBankList     = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks());
+        $maxFileSize       = Steam::phpBytes(ini_get('upload_max_filesize'));
+        $maxPostSize       = Steam::phpBytes(ini_get('post_max_size'));
+        $uploadSize        = min($maxFileSize, $maxPostSize);
+        $what              = strtolower(TransactionJournal::transactionTypeStr($journal));
+        $subTitle          = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
+        $preFilled         = [
             'date'                     => TransactionJournal::dateAsString($journal),
             'interest_date'            => TransactionJournal::dateAsString($journal, 'interest_date'),
             'book_date'                => TransactionJournal::dateAsString($journal, 'book_date'),
@@ -204,25 +184,25 @@ class TransactionController extends Controller
         }
         Session::forget('transactions.edit.fromUpdate');
 
-
         return view('transactions.edit', compact('journal', 'uploadSize', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle'))->with(
             'data', $preFilled
         );
     }
 
     /**
+     * @param Request                    $request
      * @param JournalRepositoryInterface $repository
-     * @param                            $what
+     * @param string                     $what
      *
-     * @return \Illuminate\View\View
+     * @return View
      */
-    public function index(JournalRepositoryInterface $repository, string $what)
+    public function index(Request $request, JournalRepositoryInterface $repository, string $what)
     {
         $pageSize     = Preferences::get('transactionPageSize', 50)->data;
         $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
         $types        = config('firefly.transactionTypesByWhat.' . $what);
         $subTitle     = trans('firefly.title_' . $what);
-        $page         = intval(Input::get('page'));
+        $page         = intval($request->get('page'));
         $journals     = $repository->getJournals($types, $page, $pageSize);
 
         $journals->setPath('transactions/' . $what);
@@ -294,9 +274,8 @@ class TransactionController extends Controller
      */
     public function massEdit(Collection $journals)
     {
-        $subTitle = trans('firefly.mass_edit_journals');
-        /** @var ARI $accountRepository */
-        $accountRepository = app(ARI::class);
+        $subTitle          = trans('firefly.mass_edit_journals');
+        $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface');
         $accountList       = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account']));
 
         // put previous url in session
@@ -315,14 +294,12 @@ class TransactionController extends Controller
      */
     public function massUpdate(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
     {
-        $journalIds = Input::get('journals');
+        $journalIds = $request->get('journals');
         $count      = 0;
         if (is_array($journalIds)) {
             foreach ($journalIds as $journalId) {
                 $journal = $repository->find(intval($journalId));
                 if ($journal) {
-                    // do update.
-
                     // get optional fields:
                     $what            = strtolower(TransactionJournal::transactionTypeStr($journal));
                     $sourceAccountId = $request->get('source_account_id')[$journal->id] ??  0;
@@ -379,18 +356,18 @@ class TransactionController extends Controller
     }
 
     /**
+     * @param Request                    $request
      * @param JournalRepositoryInterface $repository
      *
-     * @return \Symfony\Component\HttpFoundation\Response
+     * @return \Illuminate\Http\JsonResponse
      */
-    public function reorder(JournalRepositoryInterface $repository)
+    public function reorder(Request $request, JournalRepositoryInterface $repository)
     {
-        $ids  = Input::get('items');
-        $date = new Carbon(Input::get('date'));
+        $ids  = $request->get('items');
+        $date = new Carbon($request->get('date'));
         if (count($ids) > 0) {
             $order = 0;
             foreach ($ids as $id) {
-
                 $journal = $repository->find($id);
                 if ($journal && $journal->date->format('Y-m-d') == $date->format('Y-m-d')) {
                     $journal->order = $order;
@@ -406,72 +383,26 @@ class TransactionController extends Controller
     }
 
     /**
-     * @param TransactionJournal $journal
+     * @param TransactionJournal         $journal
+     * @param JournalRepositoryInterface $repository
      *
-     * @return \Illuminate\View\View
+     * @return View
+     * @throws FireflyException
      */
     public function show(TransactionJournal $journal, JournalRepositoryInterface $repository)
     {
+        $events       = $repository->getPiggyBankEvents($journal);
+        $transactions = $repository->getTransactions($journal);
+        $what         = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
+        $subTitle     = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
 
-        /** @var Collection $set */
-        $events = $journal->piggyBankEvents()->get();
-        $events->each(
-            function (PiggyBankEvent $event) {
-                $event->piggyBank = $event->piggyBank()->withTrashed()->first();
-            }
-        );
-
-        switch ($journal->transactionType->type) {
-            case TransactionType::DEPOSIT:
-                /** @var Collection $transactions */
-                $transactions       = $journal->transactions()
-                                              ->groupBy('transactions.account_id')
-                                              ->where('amount', '<', 0)
-                                              ->orderBy('amount', 'ASC')->get(
-                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
-                    );
-                $final              = $journal->transactions()
-                                              ->groupBy('transactions.account_id')
-                                              ->where('amount', '>', 0)
-                                              ->orderBy('amount', 'ASC')->first(
-                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
-                    );
-                $transactions->push($final);
-                break;
-            case TransactionType::WITHDRAWAL:
-                /** @var Collection $transactions */
-                $transactions       = $journal->transactions()
-                                              ->groupBy('transactions.account_id')
-                                              ->where('amount', '>', 0)
-                                              ->orderBy('amount', 'ASC')->get(
-                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
-                    );
-                $final              = $journal->transactions()
-                                              ->groupBy('transactions.account_id')
-                                              ->where('amount', '<', 0)
-                                              ->orderBy('amount', 'ASC')->first(
-                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
-                    );
-                $transactions->push($final);
-                break;
-            default:
-                throw new FireflyException('Cannot handle ' . $journal->transactionType->type);
-                break;
+        if ($transactions->count() > 2) {
+            return view('split.journals.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
         }
 
-
-        // foreach do balance thing
-        $transactions->each(
-            function (Transaction $t) use ($repository) {
-                $t->before = $repository->balanceBeforeTransaction($t);
-            }
-        );
-
-
-        $what     = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
-        $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
-
         return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
+
+
     }
 
     /**
@@ -484,16 +415,30 @@ class TransactionController extends Controller
      */
     public function store(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att)
     {
-        Log::debug('Start of store.');
         $doSplit     = intval($request->get('split_journal')) === 1;
         $journalData = $request->getJournalData();
-        if ($doSplit) {
-            // put all journal data in the session and redirect to split routine.
-            Session::put('temporary_split_data', $journalData);
 
-            return redirect(route('split.journal.from-store'));
+        // store the journal only, flash the rest.
+        if ($doSplit) {
+            $journal = $repository->storeJournal($journalData);
+
+            // store attachments:
+            $att->saveAttachmentsForModel($journal);
+
+            // flash errors
+            if (count($att->getErrors()->get('attachments')) > 0) {
+                Session::flash('error', $att->getErrors()->get('attachments'));
+            }
+            // flash messages
+            if (count($att->getMessages()->get('attachments')) > 0) {
+                Session::flash('info', $att->getMessages()->get('attachments'));
+            }
+
+            Session::put('journal-data', $journalData);
+
+            return redirect(route('split.journal.create', [$journal->id]));
         }
-        Log::debug('Not in split.');
+
 
         // if not withdrawal, unset budgetid.
         if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
@@ -501,7 +446,6 @@ class TransactionController extends Controller
         }
 
         $journal = $repository->store($journalData);
-
         $att->saveAttachmentsForModel($journal);
 
         // flash errors
@@ -518,7 +462,7 @@ class TransactionController extends Controller
         Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
         Preferences::mark();
 
-        if (intval(Input::get('create_another')) === 1) {
+        if (intval($request->get('create_another')) === 1) {
             // set value so create routine will not overwrite URL:
             Session::put('transactions.create.fromStore', true);
 
@@ -565,7 +509,7 @@ class TransactionController extends Controller
         Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($journalData['description'])])));
         Preferences::mark();
 
-        if (intval(Input::get('return_to_edit')) === 1) {
+        if (intval($request->get('return_to_edit')) === 1) {
             // set value so edit routine will not overwrite URL:
             Session::put('transactions.edit.fromUpdate', true);
 
diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php
index d98cd06a4c..09868f4316 100644
--- a/app/Http/Requests/SplitJournalFormRequest.php
+++ b/app/Http/Requests/SplitJournalFormRequest.php
@@ -40,7 +40,7 @@ class SplitJournalFormRequest extends Request
             'journal_currency_id'              => intval($this->get('journal_currency_id')),
             'journal_source_account_id'        => intval($this->get('journal_source_account_id')),
             'journal_source_account_name'      => $this->get('journal_source_account_name'),
-            'journal_destination_account_id'   => intval($this->get('journal_source_destination_id')),
+            'journal_destination_account_id'   => intval($this->get('journal_destination_account_id')),
             'journal_destination_account_name' => $this->get('journal_source_destination_name'),
             'date'                             => new Carbon($this->get('date')),
             'what'                             => $this->get('what'),
@@ -61,7 +61,7 @@ class SplitJournalFormRequest extends Request
                 'source_account_name'      => $this->get('journal_source_account_name'),
                 'destination_account_id'   => isset($this->get('destination_account_id')[$index])
                     ? intval($this->get('destination_account_id')[$index])
-                    : intval($this->get('destination_account_id')),
+                    : intval($this->get('journal_destination_account_id')),
                 'destination_account_name' => $this->get('destination_account_name')[$index] ?? '',
             ];
             $data['transactions'][] = $transaction;
diff --git a/app/Http/routes.php b/app/Http/routes.php
index 1555574eb9..ede270be5f 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -343,10 +343,11 @@ Route::group(
     /**
      * Split controller
      */
-    Route::get('/transaction/split', ['uses' => 'Transaction\SplitController@journalFromStore', 'as' => 'split.journal.from-store']);
-    Route::post('/transaction/split', ['uses' => 'Transaction\SplitController@postJournalFromStore', 'as' => 'split.journal.from-store.post']);
-    Route::get('/transaction/edit-split/{journal}',['uses' => 'Transaction\SplitController@edit', 'as' => 'split.journal.edit']);
-    Route::post('/transaction/edit-split/{journal}',['uses' => 'Transaction\SplitController@update', 'as' => 'split.journal.update']);
+
+    Route::get('/transaction/create-split/{unfinishedJournal}', ['uses' => 'Transaction\SplitController@create', 'as' => 'split.journal.create']);
+    Route::post('/transaction/store-split/{unfinishedJournal}', ['uses' => 'Transaction\SplitController@store', 'as' => 'split.journal.store']);
+    Route::get('/transaction/edit-split/{journal}', ['uses' => 'Transaction\SplitController@edit', 'as' => 'split.journal.edit']);
+    Route::post('/transaction/edit-split/{journal}', ['uses' => 'Transaction\SplitController@update', 'as' => 'split.journal.update']);
     /**
      * Tag Controller
      */
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index f24b9ea15b..3ec470e6a3 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -10,6 +10,7 @@ use FireflyIII\Models\Account;
 use FireflyIII\Models\AccountType;
 use FireflyIII\Models\Budget;
 use FireflyIII\Models\Category;
+use FireflyIII\Models\PiggyBankEvent;
 use FireflyIII\Models\Tag;
 use FireflyIII\Models\Transaction;
 use FireflyIII\Models\TransactionJournal;
@@ -208,6 +209,91 @@ class JournalRepository implements JournalRepositoryInterface
         return $set;
     }
 
+    /**
+     * @param TransactionJournal $journal
+     *
+     * @return Collection
+     */
+    public function getPiggyBankEvents(TransactionJournal $journal): Collection
+    {
+        /** @var Collection $set */
+        $events = $journal->piggyBankEvents()->get();
+        $events->each(
+            function (PiggyBankEvent $event) {
+                $event->piggyBank = $event->piggyBank()->withTrashed()->first();
+            }
+        );
+
+        return $events;
+    }
+
+    /**
+     * @param TransactionJournal $journal
+     *
+     * @return Collection
+     * @throws FireflyException
+     */
+    public function getTransactions(TransactionJournal $journal): Collection
+    {
+        switch ($journal->transactionType->type) {
+            case TransactionType::DEPOSIT:
+                /** @var Collection $transactions */
+                $transactions = $journal->transactions()
+                                        ->groupBy('transactions.account_id')
+                                        ->where('amount', '<', 0)
+                                        ->groupBy('transactions.id')
+                                        ->orderBy('amount', 'ASC')->get(
+                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+                    );
+                $final        = $journal->transactions()
+                                        ->groupBy('transactions.account_id')
+                                        ->where('amount', '>', 0)
+                                        ->orderBy('amount', 'ASC')->first(
+                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+                    );
+                $transactions->push($final);
+                break;
+            case TransactionType::TRANSFER:
+
+                /** @var Collection $transactions */
+                $transactions = $journal->transactions()
+                    ->groupBy('transactions.id')
+                    ->orderBy('transactions.id')->get(
+                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+                    );
+                break;
+            case TransactionType::WITHDRAWAL:
+
+                /** @var Collection $transactions */
+                $transactions = $journal->transactions()
+                                        ->where('amount', '>', 0)
+                                        ->groupBy('transactions.id')
+                                        ->orderBy('amount', 'ASC')->get(
+                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+                    );
+                $final        = $journal->transactions()
+                                        ->where('amount', '<', 0)
+                                        ->groupBy('transactions.account_id')
+                                        ->orderBy('amount', 'ASC')->first(
+                        ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')]
+                    );
+                $transactions->push($final);
+                break;
+            default:
+
+                throw new FireflyException('Cannot handle ' . $journal->transactionType->type);
+                break;
+        }
+        // foreach do balance thing
+        $transactions->each(
+            function (Transaction $t) {
+                $t->before = $this->balanceBeforeTransaction($t);
+            }
+        );
+
+        return $transactions;
+    }
+
     /**
      * @param array $data
      *
@@ -278,6 +364,37 @@ class JournalRepository implements JournalRepositoryInterface
 
     }
 
+    /**
+     * Store journal only, uncompleted, with attachments if necessary.
+     *
+     * @param array $data
+     *
+     * @return TransactionJournal
+     */
+    public function storeJournal(array $data): TransactionJournal
+    {
+        // find transaction type.
+        $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
+
+        // store actual journal.
+        $journal = new TransactionJournal(
+            [
+                'user_id'                 => $data['user'],
+                'transaction_type_id'     => $transactionType->id,
+                'transaction_currency_id' => $data['amount_currency_id_amount'],
+                'description'             => $data['description'],
+                'completed'               => 0,
+                'date'                    => $data['date'],
+                'interest_date'           => $data['interest_date'],
+                'book_date'               => $data['book_date'],
+                'process_date'            => $data['process_date'],
+            ]
+        );
+        $journal->save();
+
+        return $journal;
+    }
+
     /**
      * @param TransactionJournal $journal
      * @param array              $data
diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php
index 76677f8a21..f93e8f9c74 100644
--- a/app/Repositories/Journal/JournalRepositoryInterface.php
+++ b/app/Repositories/Journal/JournalRepositoryInterface.php
@@ -16,6 +16,15 @@ use Illuminate\Support\Collection;
  */
 interface JournalRepositoryInterface
 {
+    /**
+     * Returns the amount in the account before the specified transaction took place.
+     *
+     * @param Transaction $transaction
+     *
+     * @return string
+     */
+    public function balanceBeforeTransaction(Transaction $transaction): string;
+
     /**
      * Deletes a journal.
      *
@@ -41,16 +50,6 @@ interface JournalRepositoryInterface
      */
     public function first(): TransactionJournal;
 
-
-    /**
-     * Returns the amount in the account before the specified transaction took place.
-     * 
-     * @param Transaction $transaction
-     *
-     * @return string
-     */
-    public function balanceBeforeTransaction(Transaction $transaction): string;
-
     /**
      * Returns the amount in the account before the specified transaction took place.
      *
@@ -85,6 +84,20 @@ interface JournalRepositoryInterface
      */
     public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection;
 
+    /**
+     * @param TransactionJournal $journal
+     *
+     * @return Collection
+     */
+    public function getPiggyBankEvents(TransactionJournal $journal): Collection;
+
+    /**
+     * @param TransactionJournal $journal
+     *
+     * @return Collection
+     */
+    public function getTransactions(TransactionJournal $journal): Collection;
+
     /**
      * @param array $data
      *
@@ -92,6 +105,15 @@ interface JournalRepositoryInterface
      */
     public function store(array $data): TransactionJournal;
 
+    /**
+     * Store journal only, uncompleted, with attachments if necessary.
+     *
+     * @param array $data
+     *
+     * @return TransactionJournal
+     */
+    public function storeJournal(array $data): TransactionJournal;
+
     /**
      * @param TransactionJournal $journal
      * @param array              $data
diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php
index 555e49aadf..a9d7950ac7 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepository.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepository.php
@@ -3,8 +3,8 @@ declare(strict_types = 1);
 
 namespace FireflyIII\Repositories\PiggyBank;
 
+use Amount;
 use Carbon\Carbon;
-use DB;
 use FireflyIII\Models\PiggyBank;
 use FireflyIII\Models\PiggyBankEvent;
 use FireflyIII\User;
@@ -86,6 +86,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
         return $set;
     }
 
+    /**
+     * Also add amount in name.
+     *
+     * @return Collection
+     */
+    public function getPiggyBanksWithAmount() : Collection
+    {
+        $set = $this->getPiggyBanks();
+        foreach ($set as $piggy) {
+            $piggy->name = $piggy->name . ' (' . Amount::format($piggy->currentRelevantRep()->currentamount, false) . ')';
+        }
+
+        return $set;
+    }
+
     /**
      * Set all piggy banks to order 0.
      *
diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
index ba29d6f0bf..7b1e892dc1 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php
@@ -57,6 +57,13 @@ interface PiggyBankRepositoryInterface
      */
     public function getPiggyBanks() : Collection;
 
+    /**
+     * Also add amount in name.
+     *
+     * @return Collection
+     */
+    public function getPiggyBanksWithAmount() : Collection;
+
     /**
      * Set all piggy banks to order 0.
      *
@@ -86,7 +93,7 @@ interface PiggyBankRepositoryInterface
 
     /**
      * Update existing piggy bank.
-     * 
+     *
      * @param PiggyBank $piggyBank
      * @param array     $data
      *
diff --git a/app/Support/Binder/UnfinishedJournal.php b/app/Support/Binder/UnfinishedJournal.php
new file mode 100644
index 0000000000..9e54cd4906
--- /dev/null
+++ b/app/Support/Binder/UnfinishedJournal.php
@@ -0,0 +1,55 @@
+leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+                                        ->where('completed', 0)
+                                        ->where('user_id', Auth::user()->id)->first(['transaction_journals.*']);
+            if ($object) {
+                return $object;
+            }
+        }
+
+        throw new NotFoundHttpException;
+
+    }
+}
diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php
index 775ec20d55..d8d4e64e76 100644
--- a/app/Support/Migration/TestData.php
+++ b/app/Support/Migration/TestData.php
@@ -415,6 +415,73 @@ class TestData
         DB::table('transactions')->insert($transactions);
     }
 
+    /**
+     *
+     */
+    private function createMultiDeposits()
+    {
+        foreach ($this->data['multi-deposits'] as $deposit) {
+            $journalId = DB::table('transaction_journals')->insertGetId(
+                [
+                    'created_at'              => DB::raw('NOW()'),
+                    'updated_at'              => DB::raw('NOW()'),
+                    'user_id'                 => $deposit['user_id'],
+                    'transaction_type_id'     => 2,
+                    'transaction_currency_id' => 1,
+                    'description'             => Crypt::encrypt($deposit['description']),
+                    'completed'               => 1,
+                    'date'                    => $deposit['date'],
+                    'interest_date'           => $deposit['interest_date'] ?? null,
+                    'book_date'               => $deposit['book_date'] ?? null,
+                    'process_date'            => $deposit['process_date'] ?? null,
+                    'encrypted'               => 1,
+                    'order'                   => 0,
+                    'tag_count'               => 0,
+                ]
+            );
+            foreach ($deposit['source_ids'] as $index => $source) {
+                $description = $deposit['description'] . ' (#' . ($index + 1) . ')';
+                $amount      = $deposit['amounts'][$index];
+                $first       = DB::table('transactions')->insertGetId(
+                    [
+                        'created_at'             => DB::raw('NOW()'),
+                        'updated_at'             => DB::raw('NOW()'),
+                        'account_id'             => $deposit['destination_id'],
+                        'transaction_journal_id' => $journalId,
+                        'description'            => $description,
+                        'amount'                 => $amount,
+                    ]
+                );
+                $second      = DB::table('transactions')->insertGetId(
+                    [
+                        'created_at'             => DB::raw('NOW()'),
+                        'updated_at'             => DB::raw('NOW()'),
+                        'account_id'             => $source,
+                        'transaction_journal_id' => $journalId,
+                        'description'            => $description,
+                        'amount'                 => $amount * -1,
+                    ]
+                );
+                // link first and second to budget and category, if present.
+
+                if (isset($deposit['category_ids'][$index])) {
+                    DB::table('category_transaction')->insert(
+                        [
+                            'category_id'    => $deposit['category_ids'][$index],
+                            'transaction_id' => $first,
+                        ]
+                    );
+                    DB::table('category_transaction')->insert(
+                        [
+                            'category_id'    => $deposit['category_ids'][$index],
+                            'transaction_id' => $second,
+                        ]
+                    );
+                }
+            }
+        }
+    }
+
     /**
      *
      */
@@ -660,6 +727,7 @@ class TestData
         $this->createJournals();
         $this->createAttachments();
         $this->createMultiWithdrawals();
+        $this->createMultiDeposits();
     }
 
 }
diff --git a/config/firefly.php b/config/firefly.php
index fe4724a449..ea8c4c226d 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -158,28 +158,29 @@ return [
     ],
     'bindables'   => [
         // models
-        'account'         => 'FireflyIII\Models\Account',
-        'attachment'      => 'FireflyIII\Models\Attachment',
-        'bill'            => 'FireflyIII\Models\Bill',
-        'budget'          => 'FireflyIII\Models\Budget',
-        'category'        => 'FireflyIII\Models\Category',
-        'currency'        => 'FireflyIII\Models\TransactionCurrency',
-        'limitrepetition' => 'FireflyIII\Models\LimitRepetition',
-        'piggyBank'       => 'FireflyIII\Models\PiggyBank',
-        'tj'              => 'FireflyIII\Models\TransactionJournal',
-        'tag'             => 'FireflyIII\Models\Tag',
-        'rule'            => 'FireflyIII\Models\Rule',
-        'ruleGroup'       => 'FireflyIII\Models\RuleGroup',
-        'jobKey'          => 'FireflyIII\Models\ExportJob',
+        'account'           => 'FireflyIII\Models\Account',
+        'attachment'        => 'FireflyIII\Models\Attachment',
+        'bill'              => 'FireflyIII\Models\Bill',
+        'budget'            => 'FireflyIII\Models\Budget',
+        'category'          => 'FireflyIII\Models\Category',
+        'currency'          => 'FireflyIII\Models\TransactionCurrency',
+        'limitrepetition'   => 'FireflyIII\Models\LimitRepetition',
+        'piggyBank'         => 'FireflyIII\Models\PiggyBank',
+        'tj'                => 'FireflyIII\Models\TransactionJournal',
+        'unfinishedJournal' => 'FireflyIII\Support\Binder\UnfinishedJournal',
+        'tag'               => 'FireflyIII\Models\Tag',
+        'rule'              => 'FireflyIII\Models\Rule',
+        'ruleGroup'         => 'FireflyIII\Models\RuleGroup',
+        'jobKey'            => 'FireflyIII\Models\ExportJob',
         // lists
-        'accountList'     => 'FireflyIII\Support\Binder\AccountList',
-        'budgetList'      => 'FireflyIII\Support\Binder\BudgetList',
-        'journalList'     => 'FireflyIII\Support\Binder\JournalList',
-        'categoryList'    => 'FireflyIII\Support\Binder\CategoryList',
+        'accountList'       => 'FireflyIII\Support\Binder\AccountList',
+        'budgetList'        => 'FireflyIII\Support\Binder\BudgetList',
+        'journalList'       => 'FireflyIII\Support\Binder\JournalList',
+        'categoryList'      => 'FireflyIII\Support\Binder\CategoryList',
 
         // others
-        'start_date'      => 'FireflyIII\Support\Binder\Date',
-        'end_date'        => 'FireflyIII\Support\Binder\Date',
+        'start_date'        => 'FireflyIII\Support\Binder\Date',
+        'end_date'          => 'FireflyIII\Support\Binder\Date',
     ],
 
     'rule-triggers'     => [
diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
index 2bead9d151..b60433f727 100644
--- a/resources/lang/en_US/form.php
+++ b/resources/lang/en_US/form.php
@@ -10,129 +10,131 @@
 return [
 
     // new user:
-    'bank_name'                    => 'Bank name',
-    'bank_balance'                 => 'Balance',
-    'savings_balance'              => 'Savings balance',
-    'credit_card_limit'            => 'Credit card limit',
-    'automatch'                    => 'Match automatically',
-    'skip'                         => 'Skip',
-    'name'                         => 'Name',
-    'active'                       => 'Active',
-    'amount_min'                   => 'Minimum amount',
-    'amount_max'                   => 'Maximum amount',
-    'match'                        => 'Matches on',
-    'repeat_freq'                  => 'Repeats',
-    'journal_currency_id'          => 'Currency',
-    'journal_amount'               => 'Amount',
-    'journal_asset_source_account' => 'Asset account (source)',
-    'journal_source_account_id'    => 'Asset account (source)',
-    'account_from_id'              => 'From account',
-    'account_to_id'                => 'To account',
-    'asset_destination_account'    => 'Asset account (destination)',
-    'asset_source_account'         => 'Asset account (source)',
-    'journal_description'          => 'Description',
-    'split_journal'                => 'Split this transaction',
-    'split_journal_explanation'    => 'Split this transaction in multiple parts',
-    'currency'                     => 'Currency',
-    'account_id'                   => 'Asset account',
-    'budget_id'                    => 'Budget',
-    'openingBalance'               => 'Opening balance',
-    'tagMode'                      => 'Tag mode',
-    'tagPosition'                  => 'Tag location',
-    'virtualBalance'               => 'Virtual balance',
-    'longitude_latitude'           => 'Location',
-    'targetamount'                 => 'Target amount',
-    'accountRole'                  => 'Account role',
-    'openingBalanceDate'           => 'Opening balance date',
-    'ccType'                       => 'Credit card payment plan',
-    'ccMonthlyPaymentDate'         => 'Credit card monthly payment date',
-    'piggy_bank_id'                => 'Piggy bank',
-    'returnHere'                   => 'Return here',
-    'returnHereExplanation'        => 'After storing, return here to create another one.',
-    'returnHereUpdateExplanation'  => 'After updating, return here.',
-    'description'                  => 'Description',
-    'expense_account'              => 'Expense account',
-    'revenue_account'              => 'Revenue account',
-    'amount'                       => 'Amount',
-    'date'                         => 'Date',
-    'interest_date'                => 'Interest date',
-    'book_date'                    => 'Book date',
-    'process_date'                 => 'Processing date',
-    'category'                     => 'Category',
-    'tags'                         => 'Tags',
-    'deletePermanently'            => 'Delete permanently',
-    'cancel'                       => 'Cancel',
-    'targetdate'                   => 'Target date',
-    'tag'                          => 'Tag',
-    'under'                        => 'Under',
-    'symbol'                       => 'Symbol',
-    'code'                         => 'Code',
-    'iban'                         => 'IBAN',
-    'accountNumber'                => 'Account number',
-    'csv'                          => 'CSV file',
-    'has_headers'                  => 'Headers',
-    'date_format'                  => 'Date format',
-    'csv_config'                   => 'CSV import configuration',
-    'specifix'                     => 'Bank- or file specific fixes',
-    'csv_import_account'           => 'Default import account',
-    'csv_delimiter'                => 'CSV field delimiter',
-    'attachments[]'                => 'Attachments',
-    'store_new_withdrawal'         => 'Store new withdrawal',
-    'store_new_deposit'            => 'Store new deposit',
-    'store_new_transfer'           => 'Store new transfer',
-    'add_new_withdrawal'           => 'Add a new withdrawal',
-    'add_new_deposit'              => 'Add a new deposit',
-    'add_new_transfer'             => 'Add a new transfer',
-    'noPiggybank'                  => '(no piggy bank)',
-    'title'                        => 'Title',
-    'notes'                        => 'Notes',
-    'filename'                     => 'File name',
-    'mime'                         => 'Mime type',
-    'size'                         => 'Size',
-    'trigger'                      => 'Trigger',
-    'stop_processing'              => 'Stop processing',
-    'start_date'                   => 'Start of range',
-    'end_date'                     => 'End of range',
-    'export_start_range'           => 'Start of export range',
-    'export_end_range'             => 'End of export range',
-    'export_format'                => 'File format',
-    'include_attachments'          => 'Include uploaded attachments',
-    'include_config'               => 'Include configuration file',
-    'include_old_uploads'          => 'Include imported data',
-    'accounts'                     => 'Export transactions from these accounts',
-    'csv_comma'                    => 'A comma (,)',
-    'csv_semicolon'                => 'A semicolon (;)',
-    'csv_tab'                      => 'A tab (invisible)',
-    'delete_account'               => 'Delete account ":name"',
-    'delete_bill'                  => 'Delete bill ":name"',
-    'delete_budget'                => 'Delete budget ":name"',
-    'delete_category'              => 'Delete category ":name"',
-    'delete_currency'              => 'Delete currency ":name"',
-    'delete_journal'               => 'Delete transaction with description ":description"',
-    'delete_attachment'            => 'Delete attachment ":name"',
-    'delete_rule'                  => 'Delete rule ":title"',
-    'delete_rule_group'            => 'Delete rule group ":title"',
-    'attachment_areYouSure'        => 'Are you sure you want to delete the attachment named ":name"?',
-    'account_areYouSure'           => 'Are you sure you want to delete the account named ":name"?',
-    'bill_areYouSure'              => 'Are you sure you want to delete the bill named ":name"?',
-    'rule_areYouSure'              => 'Are you sure you want to delete the rule titled ":title"?',
-    'ruleGroup_areYouSure'         => 'Are you sure you want to delete the rule group titled ":title"?',
-    'budget_areYouSure'            => 'Are you sure you want to delete the budget named ":name"?',
-    'category_areYouSure'          => 'Are you sure you want to delete the category named ":name"?',
-    'currency_areYouSure'          => 'Are you sure you want to delete the currency named ":name"?',
-    'piggyBank_areYouSure'         => 'Are you sure you want to delete the piggy bank named ":name"?',
-    'journal_areYouSure'           => 'Are you sure you want to delete the transaction described ":description"?',
-    'mass_journal_are_you_sure'    => 'Are you sure you want to delete these transactions?',
-    'tag_areYouSure'               => 'Are you sure you want to delete the tag ":tag"?',
-    'permDeleteWarning'            => 'Deleting stuff from Firely is permanent and cannot be undone.',
-    'mass_make_selection'          => 'You can still prevent items from being deleted by removing the checkbox.',
-    'delete_all_permanently'       => 'Delete selected permanently',
-    'update_all_journals'          => 'Update these transactions',
-    'also_delete_transactions'     => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
-    'also_delete_rules'            => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
-    'also_delete_piggyBanks'       => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
-    'bill_keep_transactions'       => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
-    'budget_keep_transactions'     => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
-    'category_keep_transactions'   => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
-    'tag_keep_transactions'        => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
+    'bank_name'                      => 'Bank name',
+    'bank_balance'                   => 'Balance',
+    'savings_balance'                => 'Savings balance',
+    'credit_card_limit'              => 'Credit card limit',
+    'automatch'                      => 'Match automatically',
+    'skip'                           => 'Skip',
+    'name'                           => 'Name',
+    'active'                         => 'Active',
+    'amount_min'                     => 'Minimum amount',
+    'amount_max'                     => 'Maximum amount',
+    'match'                          => 'Matches on',
+    'repeat_freq'                    => 'Repeats',
+    'journal_currency_id'            => 'Currency',
+    'journal_amount'                 => 'Amount',
+    'journal_asset_source_account'   => 'Asset account (source)',
+    'journal_source_account_name'    => 'Revenue account (source)',
+    'journal_source_account_id'      => 'Asset account (source)',
+    'account_from_id'                => 'From account',
+    'account_to_id'                  => 'To account',
+    'journal_destination_account_id' => 'Asset account (destination)',
+    'asset_destination_account'      => 'Asset account (destination)',
+    'asset_source_account'           => 'Asset account (source)',
+    'journal_description'            => 'Description',
+    'split_journal'                  => 'Split this transaction',
+    'split_journal_explanation'      => 'Split this transaction in multiple parts',
+    'currency'                       => 'Currency',
+    'account_id'                     => 'Asset account',
+    'budget_id'                      => 'Budget',
+    'openingBalance'                 => 'Opening balance',
+    'tagMode'                        => 'Tag mode',
+    'tagPosition'                    => 'Tag location',
+    'virtualBalance'                 => 'Virtual balance',
+    'longitude_latitude'             => 'Location',
+    'targetamount'                   => 'Target amount',
+    'accountRole'                    => 'Account role',
+    'openingBalanceDate'             => 'Opening balance date',
+    'ccType'                         => 'Credit card payment plan',
+    'ccMonthlyPaymentDate'           => 'Credit card monthly payment date',
+    'piggy_bank_id'                  => 'Piggy bank',
+    'returnHere'                     => 'Return here',
+    'returnHereExplanation'          => 'After storing, return here to create another one.',
+    'returnHereUpdateExplanation'    => 'After updating, return here.',
+    'description'                    => 'Description',
+    'expense_account'                => 'Expense account',
+    'revenue_account'                => 'Revenue account',
+    'amount'                         => 'Amount',
+    'date'                           => 'Date',
+    'interest_date'                  => 'Interest date',
+    'book_date'                      => 'Book date',
+    'process_date'                   => 'Processing date',
+    'category'                       => 'Category',
+    'tags'                           => 'Tags',
+    'deletePermanently'              => 'Delete permanently',
+    'cancel'                         => 'Cancel',
+    'targetdate'                     => 'Target date',
+    'tag'                            => 'Tag',
+    'under'                          => 'Under',
+    'symbol'                         => 'Symbol',
+    'code'                           => 'Code',
+    'iban'                           => 'IBAN',
+    'accountNumber'                  => 'Account number',
+    'csv'                            => 'CSV file',
+    'has_headers'                    => 'Headers',
+    'date_format'                    => 'Date format',
+    'csv_config'                     => 'CSV import configuration',
+    'specifix'                       => 'Bank- or file specific fixes',
+    'csv_import_account'             => 'Default import account',
+    'csv_delimiter'                  => 'CSV field delimiter',
+    'attachments[]'                  => 'Attachments',
+    'store_new_withdrawal'           => 'Store new withdrawal',
+    'store_new_deposit'              => 'Store new deposit',
+    'store_new_transfer'             => 'Store new transfer',
+    'add_new_withdrawal'             => 'Add a new withdrawal',
+    'add_new_deposit'                => 'Add a new deposit',
+    'add_new_transfer'               => 'Add a new transfer',
+    'noPiggybank'                    => '(no piggy bank)',
+    'title'                          => 'Title',
+    'notes'                          => 'Notes',
+    'filename'                       => 'File name',
+    'mime'                           => 'Mime type',
+    'size'                           => 'Size',
+    'trigger'                        => 'Trigger',
+    'stop_processing'                => 'Stop processing',
+    'start_date'                     => 'Start of range',
+    'end_date'                       => 'End of range',
+    'export_start_range'             => 'Start of export range',
+    'export_end_range'               => 'End of export range',
+    'export_format'                  => 'File format',
+    'include_attachments'            => 'Include uploaded attachments',
+    'include_config'                 => 'Include configuration file',
+    'include_old_uploads'            => 'Include imported data',
+    'accounts'                       => 'Export transactions from these accounts',
+    'csv_comma'                      => 'A comma (,)',
+    'csv_semicolon'                  => 'A semicolon (;)',
+    'csv_tab'                        => 'A tab (invisible)',
+    'delete_account'                 => 'Delete account ":name"',
+    'delete_bill'                    => 'Delete bill ":name"',
+    'delete_budget'                  => 'Delete budget ":name"',
+    'delete_category'                => 'Delete category ":name"',
+    'delete_currency'                => 'Delete currency ":name"',
+    'delete_journal'                 => 'Delete transaction with description ":description"',
+    'delete_attachment'              => 'Delete attachment ":name"',
+    'delete_rule'                    => 'Delete rule ":title"',
+    'delete_rule_group'              => 'Delete rule group ":title"',
+    'attachment_areYouSure'          => 'Are you sure you want to delete the attachment named ":name"?',
+    'account_areYouSure'             => 'Are you sure you want to delete the account named ":name"?',
+    'bill_areYouSure'                => 'Are you sure you want to delete the bill named ":name"?',
+    'rule_areYouSure'                => 'Are you sure you want to delete the rule titled ":title"?',
+    'ruleGroup_areYouSure'           => 'Are you sure you want to delete the rule group titled ":title"?',
+    'budget_areYouSure'              => 'Are you sure you want to delete the budget named ":name"?',
+    'category_areYouSure'            => 'Are you sure you want to delete the category named ":name"?',
+    'currency_areYouSure'            => 'Are you sure you want to delete the currency named ":name"?',
+    'piggyBank_areYouSure'           => 'Are you sure you want to delete the piggy bank named ":name"?',
+    'journal_areYouSure'             => 'Are you sure you want to delete the transaction described ":description"?',
+    'mass_journal_are_you_sure'      => 'Are you sure you want to delete these transactions?',
+    'tag_areYouSure'                 => 'Are you sure you want to delete the tag ":tag"?',
+    'permDeleteWarning'              => 'Deleting stuff from Firely is permanent and cannot be undone.',
+    'mass_make_selection'            => 'You can still prevent items from being deleted by removing the checkbox.',
+    'delete_all_permanently'         => 'Delete selected permanently',
+    'update_all_journals'            => 'Update these transactions',
+    'also_delete_transactions'       => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
+    'also_delete_rules'              => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
+    'also_delete_piggyBanks'         => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
+    'bill_keep_transactions'         => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.',
+    'budget_keep_transactions'       => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.',
+    'category_keep_transactions'     => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.',
+    'tag_keep_transactions'          => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.',
 ];
diff --git a/resources/views/split/journals/from-store.twig b/resources/views/split/journals/create.twig
similarity index 74%
rename from resources/views/split/journals/from-store.twig
rename to resources/views/split/journals/create.twig
index 3c62e07552..ccb615c0ee 100644
--- a/resources/views/split/journals/from-store.twig
+++ b/resources/views/split/journals/create.twig
@@ -4,37 +4,13 @@
     {{ Breadcrumbs.renderIfExists }}
 {% endblock %}
 {% block content %}
-    {{ Form.open({'class' : 'form-horizontal','id' : 'store','url' : route('split.journal.from-store.post')}) }}
-    
-    
-
-
-
-

{{ ('split_title_'~preFilled.what)|_ }}

+ + + + + -
- -
-
-
-

- {{ ('split_intro_one_'~preFilled.what)|_ }} -

-

- {{ ('split_intro_two_'~preFilled.what)|_ }} -

-

- {% if preFilled.what =='deposit' %} - {{ trans(('firefly.split_intro_three_'~preFilled.what), {total: 500|formatAmount, split_one: 425|formatAmount, split_two: 75|formatAmount})|raw }} - {% else %} - {{ trans(('firefly.split_intro_three_'~preFilled.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }} - {% endif %} -

-

This feature is experimental.

-
-
-
-
{% if errors.all()|length > 0 %}
@@ -67,20 +43,19 @@
- {{ ExpandedForm.text('journal_description') }} - {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.journal_currency_id) }} - {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount) }} + {{ ExpandedForm.text('journal_description', journal.description) }} + {{ ExpandedForm.select('journal_currency_id', currencies, journal.transaction_currency_id) }} + {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} - - + {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} {% endif %} - + {% if preFilled.what == 'deposit' %} {{ ExpandedForm.text('journal_source_account_name', preFilled.journal_source_account_name) }} {% endif %} - + {% if preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} {% endif %} @@ -97,15 +72,16 @@
- {{ ExpandedForm.date('date', preFilled.date) }} + {{ ExpandedForm.date('date', journal.date) }} - {{ ExpandedForm.date('interest_date', preFilled.interest_date) }} + {{ ExpandedForm.date('interest_date', journal.interest_date) }} - {{ ExpandedForm.date('book_date', preFilled.book_date) }} + {{ ExpandedForm.date('book_date', journal.book_date) }} - {{ ExpandedForm.date('process_date', preFilled.process_date) }} + {{ ExpandedForm.date('process_date', journal.process_date) }}
+
@@ -119,22 +95,11 @@
-

- {{ ('split_table_intro_'~preFilled.what)|_ }} -

- - - - {% if preFilled.what == 'withdrawal' or preFilled.what == 'deposit' %} {% endif %} @@ -175,7 +140,15 @@ {% if preFilled.what == 'withdrawal' %} {% endif %} - - - - + + {% if preFilled.what == 'withdrawal' or preFilled.what == 'deposit' %} {% endif %} + + + {% if preFilled.what == 'withdrawal' %} {% endif %} - {% if preFilled.what == 'transfer' %} - - {% endif %} @@ -165,11 +162,6 @@ - {% if preFilled.what == 'transfer' %} - - {% endif %} {% endfor %} diff --git a/resources/views/split/journals/show.twig b/resources/views/split/journals/show.twig new file mode 100644 index 0000000000..f3aa827e45 --- /dev/null +++ b/resources/views/split/journals/show.twig @@ -0,0 +1,206 @@ +{% extends "./layout/default.twig" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, journal) }} +{% endblock %} + +{% block content %} +
+
+
+
+

Metadata

+
+
+
{{ trans('list.split_number') }} {{ trans('list.description') }}{{ trans('list.destination') }} - {{ Form.select('budget_id[]', budgets, preFilled.budget_id[index], {class: 'form-control'}) }} + @@ -189,11 +162,40 @@
{{ 'add_another_split'|_ }}

-

+ + + + + +

+
+ +
+
+

{{ 'optionalFields'|_ }}

+
+
+ + {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} +
+
+
+
+ +
+
+

{{ 'options'|_ }}

+
+
+ {{ ExpandedForm.optionsList('create','split-transaction') }} +
+
diff --git a/resources/views/split/journals/edit.twig b/resources/views/split/journals/edit.twig index 6299967e51..4d968e590b 100644 --- a/resources/views/split/journals/edit.twig +++ b/resources/views/split/journals/edit.twig @@ -47,14 +47,17 @@ {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.transaction_currency_id) }} {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} + {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} {% endif %} + {% if preFilled.what == 'deposit' %} {{ ExpandedForm.text('journal_source_account_name', preFilled.journal_source_account_name) }} {% endif %} + {% if preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} @@ -100,25 +103,19 @@
{{ trans('list.split_number') }} {{ trans('list.description') }}{{ trans('list.destination') }}{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}{{ trans('list.piggy_bank') }}
- {{ Form.select('piggy_bank_id[]', piggyBanks, preFilled.piggy_bank_id[index], {class: 'form-control'}) }} -
+ + + + + {% if journal.interest_date %} + + + + + {% endif %} + {% if journal.book_date %} + + + + + {% endif %} + {% if journal.process_date %} + + + + + {% endif %} + + + + + + + + + + {% for budget in journal.budgets %} + + + + + {% endfor %} + {% for category in journal.categories %} + + + + + {% endfor %} + {% if journal.bill %} + + + + + {% endif %} + {% if journal.tags|length > 0 %} + + + + + {% endif %} +
{{ trans('list.date') }}{{ journal.date.formatLocalized(monthAndDayFormat) }}
{{ trans('list.interest_date') }}{{ journal.interest_date.formatLocalized(monthAndDayFormat) }}
{{ trans('list.book_date') }}{{ journal.book_date.formatLocalized(monthAndDayFormat) }}
{{ trans('list.process_date') }}{{ journal.process_date.formatLocalized(monthAndDayFormat) }}
{{ trans('list.type') }}{{ journal.transactiontype.type|_ }}
{{ trans('list.completed') }} + {% if journal.completed %} + {{ 'yes'|_ }} + {% else %} + {{ 'no'|_ }} + {% endif %} +
{{ 'budget'|_ }}{{ budget.name }}
{{ 'category'|_ }}{{ category.name }}
{{ 'bill'|_ }}{{ journal.bill.name }}
{{ 'tags'|_ }} + {% for tag in journal.tags %} + +

+ {% if tag.tagMode == 'nothing' %} + + {% endif %} + {% if tag.tagMode == 'balancingAct' %} + + {% endif %} + {% if tag.tagMode == 'advancePayment' %} + + {% endif %} + {{ tag.tag }} +

+ {% endfor %} +
+
+ +
+ + + {% if journal.piggyBankEvents|length > 0 %} +
+
+

{{ 'piggyBanks'|_ }}

+
+
+ {% include 'list/piggy-bank-events' with {'events': events, 'showPiggyBank':true} %} +
+
+ {% endif %} +
+
+ {% if journal.attachments|length > 0 %} +
+
+

{{ 'attachments'|_ }}

+
+
+ + {% for att in journal.attachments %} + + + + + + {% endfor %} +
+
+ + +
+
+ + + {% if att.title %} + {{ att.title }} + {% else %} + {{ att.filename }} + {% endif %} + + ({{ att.size|filesize }}) + {% if att.description %} +
+ {{ att.description }} + {% endif %} +
+ +
+
+
+ {% endif %} +
+
+ + +
+
+ +
+
+

Transactions

+
+ + + + + + + + + + + + + {% for index, t in transactions %} + + + + + + + + + {% endfor %} + +
{{ trans('list.description') }}{{ trans('list.account') }}{{ trans('list.amount') }}{{ trans('list.new_balance') }}{{ trans('list.budget') }}{{ trans('list.category') }}
+ {% if (index+1) != transactions|length or what == 'transfer' %} + {{ t.description }} + {% endif %} + {{ t.account.name }} ({{ t.account.accounttype.type|_ }}){{ t.sum|formatAmount }}{{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }} + {% if (index+1) != transactions|length or what == 'transfer' %} + {{ transactionBudgets(t)|raw }} + {% endif %} + + {% if (index+1) != transactions|length or what == 'transfer' %} + {{ transactionCategories(t)|raw }} + {% endif %} +
+
+
+
+ +{% endblock %} diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 35bcbe9c2c..581c4d365a 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -101,7 +101,7 @@
- {% if journal.attachments|length > 0 and transactions.count == 2 %} + {% if journal.attachments|length > 0 %}

{{ 'attachments'|_ }}

@@ -154,7 +154,6 @@ {% endif %}
- {% if transactions.count == 2 %} {% for t in transactions %}
@@ -199,100 +198,6 @@
{% endfor %} - {% endif %} - - - {% if journal.attachments|length > 0 and transactions.count > 2 %} -
-
-

{{ 'attachments'|_ }}

-
-
- - {% for att in journal.attachments %} - - - - - - {% endfor %} -
-
- - -
-
- - - {% if att.title %} - {{ att.title }} - {% else %} - {{ att.filename }} - {% endif %} - - ({{ att.size|filesize }}) - {% if att.description %} -
- {{ att.description }} - {% endif %} -
- -
-
-
- {% endif %} -
- - - {% if transactions.count > 2 %} -
-
- -
-
-

Transactions

-
- - - - - - - - - - - - - {% for index, t in transactions %} - - - - - - - - - {% endfor %} - -
{{ trans('list.description') }}{{ trans('list.account') }}{{ trans('list.amount') }}{{ trans('list.new_balance') }}{{ trans('list.budget') }}{{ trans('list.category') }}
- {% if (index+1) != transactions|length %} - {{ t.description }} - {% endif %} - {{ t.account.name }} ({{ t.account.accounttype.type|_ }}){{ t.sum|formatAmount }}{{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }} - {% if (index+1) != transactions|length %} - {{ transactionBudgets(t)|raw }} - {% endif %} - - {% if (index+1) != transactions|length %} - {{ transactionCategories(t)|raw }} - {% endif %} -
-
-
-
- {% endif %} - {% endblock %} diff --git a/storage/database/seed.local.json b/storage/database/seed.local.json index 68bdbf2164..c69518d8c3 100644 --- a/storage/database/seed.local.json +++ b/storage/database/seed.local.json @@ -917,6 +917,47 @@ ] } ], - "multi-deposits": [], + "multi-deposits": [ + { + "user_id": 1, + "date": "2016-03-02", + "description": "Even multi-deposit (50, 50)", + "source_ids": [ + 35, + 36 + ], + "destination_id": 1, + "amounts": [ + 50, + 50 + ], + "category_ids": [ + 7, + 8, + 9 + ] + }, + { + "user_id": 1, + "date": "2016-05-02", + "description": "Uneven multi-deposit (15,34,51)", + "source_ids": [ + 35, + 36, + 37 + ], + "destination_id": 1, + "amounts": [ + 14, + 35, + 51 + ], + "category_ids": [ + 7, + 8, + 9 + ] + } + ], "multi-transfers": [] } \ No newline at end of file From 823839fbf622fcdbb15b2c2ad2a2ce79aca2e572 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 12:26:40 +0200 Subject: [PATCH 130/206] Better routes and titles. --- .../Transaction/SplitController.php | 13 ++- .../Controllers/TransactionController.php | 3 +- app/Http/breadcrumbs.php | 18 ++++ app/Support/Migration/TestData.php | 65 +++++++++++ resources/lang/en_US/firefly.php | 4 +- resources/views/split/journals/create.twig | 2 +- resources/views/split/journals/edit.twig | 2 +- resources/views/split/journals/show.twig | 102 +++++++++--------- storage/database/seed.local.json | 49 ++++++++- 9 files changed, 202 insertions(+), 56 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index d942b44664..84858d3b61 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -58,6 +58,8 @@ class SplitController extends Controller $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $subTitle = trans('form.add_new_' . $sessionData['what']); + $subTitleIcon = 'fa-plus'; $preFilled = [ 'what' => $sessionData['what'] ?? 'withdrawal', 'journal_amount' => $sessionData['amount'] ?? 0, @@ -71,7 +73,9 @@ class SplitController extends Controller 'category' => [$sessionData['category']], ]; - return view('split.journals.create', compact('journal', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize')); + return view( + 'split.journals.create', compact('journal', 'subTitle', 'subTitleIcon', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize') + ); } /** @@ -90,6 +94,8 @@ class SplitController extends Controller $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); $preFilled = $this->arrayFromJournal($request, $journal); + $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); + $subTitleIcon = 'fa-pencil'; Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']); @@ -102,7 +108,10 @@ class SplitController extends Controller return view( 'split.journals.edit', - compact('currencies', 'preFilled', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts', 'budgets', 'journal') + compact( + 'subTitleIcon', 'currencies', 'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts', + 'budgets', 'journal' + ) ); } diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index ec0a137efb..24c1193125 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -68,6 +68,7 @@ class TransactionController extends Controller $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); $preFilled = Session::has('preFilled') ? session('preFilled') : []; $subTitle = trans('form.add_new_' . $what); + $subTitleIcon = 'fa-plus'; Session::put('preFilled', $preFilled); @@ -84,7 +85,7 @@ class TransactionController extends Controller asort($piggies); - return view('transactions.create', compact('assetAccounts', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle')); + return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle')); } /** diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index a8364edd5e..9d93656e25 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -502,6 +502,7 @@ Breadcrumbs::register( } ); + /** * TAGS */ @@ -579,3 +580,20 @@ Breadcrumbs::register( } ); + +/** + * SPLIT + */ +Breadcrumbs::register( + 'split.journal.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { + $breadcrumbs->parent('transactions.show', $journal); + $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('split.journal.edit', [$journal->id])); +} +); + +Breadcrumbs::register( + 'split.journal.create', function (BreadCrumbGenerator $breadcrumbs, string $what) { + $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('split.journal.create', [$what])); +} +); \ No newline at end of file diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index d8d4e64e76..9da28f94a6 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -482,6 +482,70 @@ class TestData } } + private function createMultiTransfers() + { + foreach ($this->data['multi-transfers'] as $transfer) { + $journalId = DB::table('transaction_journals')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'user_id' => $transfer['user_id'], + 'transaction_type_id' => 3, + 'transaction_currency_id' => 1, + 'description' => Crypt::encrypt($transfer['description']), + 'completed' => 1, + 'date' => $transfer['date'], + 'interest_date' => $transfer['interest_date'] ?? null, + 'book_date' => $transfer['book_date'] ?? null, + 'process_date' => $transfer['process_date'] ?? null, + 'encrypted' => 1, + 'order' => 0, + 'tag_count' => 0, + ] + ); + foreach ($transfer['destination_ids'] as $index => $destination) { + $description = $transfer['description'] . ' (#' . ($index + 1) . ')'; + $amount = $transfer['amounts'][$index]; + $source = $transfer['source_ids'][$index]; + $first = DB::table('transactions')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'account_id' => $source, + 'transaction_journal_id' => $journalId, + 'description' => $description, + 'amount' => $amount * -1, + ] + ); + $second = DB::table('transactions')->insertGetId( + [ + 'created_at' => DB::raw('NOW()'), + 'updated_at' => DB::raw('NOW()'), + 'account_id' => $destination, + 'transaction_journal_id' => $journalId, + 'description' => $description, + 'amount' => $amount, + ] + ); + + if (isset($transfer['category_ids'][$index])) { + DB::table('category_transaction')->insert( + [ + 'category_id' => $transfer['category_ids'][$index], + 'transaction_id' => $first, + ] + ); + DB::table('category_transaction')->insert( + [ + 'category_id' => $transfer['category_ids'][$index], + 'transaction_id' => $second, + ] + ); + } + } + } + } + /** * */ @@ -728,6 +792,7 @@ class TestData $this->createAttachments(); $this->createMultiWithdrawals(); $this->createMultiDeposits(); + $this->createMultiTransfers(); } } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 7bd6d7fd79..b886ce6817 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -70,6 +70,8 @@ return [ 'registered' => 'You have registered successfully!', 'search' => 'Search', 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'source_accounts' => 'Source account(s)', + 'destination_accounts' => 'Destination account(s)', // repeat frequencies: 'repeat_freq_monthly' => 'monthly', @@ -811,7 +813,7 @@ return [ 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', 'store_splitted_withdrawal' => 'Store splitted withdrawal', - 'update_splitted_withdrawal' => 'Update splitted withdrawal', + 'update_splitted_withdrawal' => 'Update splitted withdrawal', 'split_title_deposit' => 'Split your new deposit', 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', diff --git a/resources/views/split/journals/create.twig b/resources/views/split/journals/create.twig index ccb615c0ee..2240809186 100644 --- a/resources/views/split/journals/create.twig +++ b/resources/views/split/journals/create.twig @@ -1,7 +1,7 @@ {% extends "./layout/default.twig" %} {% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists }} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, preFilled.what) }} {% endblock %} {% block content %} Metadata
- +
- + {% if journal.interest_date %} @@ -50,18 +50,22 @@ {% endif %} - {% for budget in journal.budgets %} - - - - - {% endfor %} - {% for category in journal.categories %} - - - - - {% endfor %} + + + + + + + + + + + + + + + + {% if journal.bill %} @@ -114,43 +118,43 @@
{% if journal.attachments|length > 0 %} -
-
-

{{ 'attachments'|_ }}

-
-
-
{{ trans('list.date') }}{{ trans('list.date') }} {{ journal.date.formatLocalized(monthAndDayFormat) }}
{{ 'budget'|_ }}{{ budget.name }}
{{ 'category'|_ }}{{ category.name }}
{{ 'budgets'|_ }}{{ journalBudgets(journal)|raw }}
{{ 'categories'|_ }}{{ journalCategories(journal)|raw }}
{{ 'source_accounts'|_ }}{{ sourceAccount(journal)|raw }}
{{ 'destination_accounts'|_ }}{{ destinationAccount(journal)|raw }}
{{ 'bill'|_ }}
- {% for att in journal.attachments %} - - - + + + {% endfor %} +
-
- - -
-
- - - {% if att.title %} - {{ att.title }} - {% else %} - {{ att.filename }} +
+
+

{{ 'attachments'|_ }}

+
+
+ + {% for att in journal.attachments %} + + + - - - {% endfor %} -
+
+ + +
+
+ + + {% if att.title %} + {{ att.title }} + {% else %} + {{ att.filename }} + {% endif %} + + ({{ att.size|filesize }}) + {% if att.description %} +
+ {{ att.description }} {% endif %} - - ({{ att.size|filesize }}) - {% if att.description %} -
- {{ att.description }} - {% endif %} -
- -
+
+ +
+
-
{% endif %}
diff --git a/storage/database/seed.local.json b/storage/database/seed.local.json index c69518d8c3..740ca45205 100644 --- a/storage/database/seed.local.json +++ b/storage/database/seed.local.json @@ -959,5 +959,52 @@ ] } ], - "multi-transfers": [] + "multi-transfers": [ + { + "user_id": 1, + "date": "2016-03-02", + "description": "Even multi-transfer (50, 50)", + "source_ids": [ + 4, + 4 + ], + "destination_ids": [ + 5, + 5 + ], + "amounts": [ + 50, + 50 + ], + "category_ids": [ + 7, + 8 + ] + }, + { + "user_id": 1, + "date": "2016-05-02", + "description": "Uneven multi-transfer (15,34,51)", + "source_ids": [ + 4, + 4, + 4 + ], + "destination_ids": [ + 5, + 5, + 5 + ], + "amounts": [ + 14, + 35, + 51 + ], + "category_ids": [ + 7, + 8, + 9 + ] + } + ] } \ No newline at end of file From a0aa114ee6e28c7532d6012cb6d9f873b52eb2bb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 12:32:18 +0200 Subject: [PATCH 131/206] Update balance view. [skip ci] --- resources/views/transactions/show.twig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 581c4d365a..74b1685f13 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -170,12 +170,8 @@ {{ t.account.accounttype.type|_ }} - {{ 'amount'|_ }} - {{ t.before|formatAmount }} - - - {{ 'newBalance'|_ }} - {{ (t.before+t.amount)|formatAmount }} + {{ 'balance'|_ }} + {{ t.before|formatAmount }} → {{ (t.before+t.amount)|formatAmount }} {% if t.description %} From 5065b1ee038ec5779c239ff3c2ef8bafe443464f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 12:39:39 +0200 Subject: [PATCH 132/206] Enable cache [skip ci] --- app/Support/Twig/Journal.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 7b79770044..a351385bfa 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -165,7 +165,7 @@ class Journal extends Twig_Extension $cache->addProperty('transaction-journal'); $cache->addProperty('budget-string'); if ($cache->has()) { - //return $cache->get(); + return $cache->get(); } @@ -240,7 +240,7 @@ class Journal extends Twig_Extension $cache->addProperty('transaction'); $cache->addProperty('budget-string'); if ($cache->has()) { - // return $cache->get(); + return $cache->get(); } $budgets = []; @@ -270,7 +270,7 @@ class Journal extends Twig_Extension $cache->addProperty('transaction'); $cache->addProperty('category-string'); if ($cache->has()) { - // return $cache->get(); + return $cache->get(); } From a84de5db7718a80dc433cda1dd94dd01842168f0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 14:48:21 +0200 Subject: [PATCH 133/206] Enable caching. Remove stuff for development. --- CHANGELOG.md | 19 ++ _development/.coveralls.yml | 1 - _development/.csslintrc | 2 - _development/.eslintignore | 1 - _development/.eslintrc | 213 ------------ _development/.jshintrc | 22 -- _development/codesniffer/ruleset.xml | 27 -- _development/codestyle.xml | 322 ------------------ _development/cover.sh | 63 ---- _development/favicon.pxm | Bin 102164 -> 0 bytes _development/gulpfile.js | 16 - _development/phpmd/phpmd.xml | 55 --- _development/phpspec.yml | 5 - _development/phpunit.cover.xml | 63 ---- _development/phpunit.default.xml | 31 -- _development/phpunit.xml | 31 -- _development/pu.sh | 103 ------ _development/readme.txt | 1 - app/Http/Controllers/AccountController.php | 4 +- .../Controllers/Chart/AccountController.php | 8 +- .../Controllers/Chart/CategoryController.php | 2 +- .../Controllers/Chart/ReportController.php | 2 +- 22 files changed, 27 insertions(+), 964 deletions(-) delete mode 100644 _development/.coveralls.yml delete mode 100644 _development/.csslintrc delete mode 100644 _development/.eslintignore delete mode 100644 _development/.eslintrc delete mode 100644 _development/.jshintrc delete mode 100644 _development/codesniffer/ruleset.xml delete mode 100644 _development/codestyle.xml delete mode 100755 _development/cover.sh delete mode 100644 _development/favicon.pxm delete mode 100644 _development/gulpfile.js delete mode 100644 _development/phpmd/phpmd.xml delete mode 100644 _development/phpspec.yml delete mode 100644 _development/phpunit.cover.xml delete mode 100644 _development/phpunit.default.xml delete mode 100644 _development/phpunit.xml delete mode 100755 _development/pu.sh delete mode 100644 _development/readme.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 412b2d343d..0b5e4cf7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - No unreleased changes yet. +[3.9.0] +### Added +- @zjean has added code that allows you to force "https://"-URL's. +- @tonicospinelli has added Portuguese (Brazil) translations. +- Firefly III supports the *splitting* of transactions: + - A withdrawal (expense) can be split into multiple sub-transactions (with multiple destinations) + - Likewise for deposits (incomes). You can set multiple sources. + - Likewise for transfers. + +### Changed +- Update a lot of libraries. +- Big improvement to test data generation. +- Cleaned up many repositories. + +### Removed +- Front page boxes will no longer respond to credit card bills. + +### Fixed +- Many bugs ## [3.8.4] - 2016-04-24 ### Added diff --git a/_development/.coveralls.yml b/_development/.coveralls.yml deleted file mode 100644 index eb8e63b57e..0000000000 --- a/_development/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -src_dir: . diff --git a/_development/.csslintrc b/_development/.csslintrc deleted file mode 100644 index aacba956e5..0000000000 --- a/_development/.csslintrc +++ /dev/null @@ -1,2 +0,0 @@ ---exclude-exts=.min.css ---ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes diff --git a/_development/.eslintignore b/_development/.eslintignore deleted file mode 100644 index 96212a3593..0000000000 --- a/_development/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/*{.,-}min.js diff --git a/_development/.eslintrc b/_development/.eslintrc deleted file mode 100644 index 9faa37508e..0000000000 --- a/_development/.eslintrc +++ /dev/null @@ -1,213 +0,0 @@ -ecmaFeatures: - modules: true - jsx: true - -env: - amd: true - browser: true - es6: true - jquery: true - node: true - -# http://eslint.org/docs/rules/ -rules: - # Possible Errors - comma-dangle: [2, never] - no-cond-assign: 2 - no-console: 0 - no-constant-condition: 2 - no-control-regex: 2 - no-debugger: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-empty: 2 - no-empty-character-class: 2 - no-ex-assign: 2 - no-extra-boolean-cast: 2 - no-extra-parens: 0 - no-extra-semi: 2 - no-func-assign: 2 - no-inner-declarations: [2, functions] - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-negated-in-lhs: 2 - no-obj-calls: 2 - no-regex-spaces: 2 - no-sparse-arrays: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - use-isnan: 2 - valid-jsdoc: 0 - valid-typeof: 2 - - # Best Practices - accessor-pairs: 2 - block-scoped-var: 0 - complexity: [2, 6] - consistent-return: 0 - curly: 0 - default-case: 0 - dot-location: 0 - dot-notation: 0 - eqeqeq: 2 - guard-for-in: 2 - no-alert: 2 - no-caller: 2 - no-case-declarations: 2 - no-div-regex: 2 - no-else-return: 0 - no-empty-label: 2 - no-empty-pattern: 2 - no-eq-null: 2 - no-eval: 2 - no-extend-native: 2 - no-extra-bind: 2 - no-fallthrough: 2 - no-floating-decimal: 0 - no-implicit-coercion: 0 - no-implied-eval: 2 - no-invalid-this: 0 - no-iterator: 2 - no-labels: 0 - no-lone-blocks: 2 - no-loop-func: 2 - no-magic-number: 0 - no-multi-spaces: 0 - no-multi-str: 0 - no-native-reassign: 2 - no-new-func: 2 - no-new-wrappers: 2 - no-new: 2 - no-octal-escape: 2 - no-octal: 2 - no-proto: 2 - no-redeclare: 2 - no-return-assign: 2 - no-script-url: 2 - no-self-compare: 2 - no-sequences: 0 - no-throw-literal: 0 - no-unused-expressions: 2 - no-useless-call: 2 - no-useless-concat: 2 - no-void: 2 - no-warning-comments: 0 - no-with: 2 - radix: 2 - vars-on-top: 0 - wrap-iife: 2 - yoda: 0 - - # Strict - strict: 0 - - # Variables - init-declarations: 0 - no-catch-shadow: 2 - no-delete-var: 2 - no-label-var: 2 - no-shadow-restricted-names: 2 - no-shadow: 0 - no-undef-init: 2 - no-undef: 0 - no-undefined: 0 - no-unused-vars: 0 - no-use-before-define: 0 - - # Node.js and CommonJS - callback-return: 2 - global-require: 2 - handle-callback-err: 2 - no-mixed-requires: 0 - no-new-require: 0 - no-path-concat: 2 - no-process-exit: 2 - no-restricted-modules: 0 - no-sync: 0 - - # Stylistic Issues - array-bracket-spacing: 0 - block-spacing: 0 - brace-style: 0 - camelcase: 0 - comma-spacing: 0 - comma-style: 0 - computed-property-spacing: 0 - consistent-this: 0 - eol-last: 0 - func-names: 0 - func-style: 0 - id-length: 0 - id-match: 0 - indent: 0 - jsx-quotes: 0 - key-spacing: 0 - linebreak-style: 0 - lines-around-comment: 0 - max-depth: 0 - max-len: 0 - max-nested-callbacks: 0 - max-params: 0 - max-statements: [2, 30] - new-cap: 0 - new-parens: 0 - newline-after-var: 0 - no-array-constructor: 0 - no-bitwise: 0 - no-continue: 0 - no-inline-comments: 0 - no-lonely-if: 0 - no-mixed-spaces-and-tabs: 0 - no-multiple-empty-lines: 0 - no-negated-condition: 0 - no-nested-ternary: 0 - no-new-object: 0 - no-plusplus: 0 - no-restricted-syntax: 0 - no-spaced-func: 0 - no-ternary: 0 - no-trailing-spaces: 0 - no-underscore-dangle: 0 - no-unneeded-ternary: 0 - object-curly-spacing: 0 - one-var: 0 - operator-assignment: 0 - operator-linebreak: 0 - padded-blocks: 0 - quote-props: 0 - quotes: 0 - require-jsdoc: 0 - semi-spacing: 0 - semi: 0 - sort-vars: 0 - space-after-keywords: 0 - space-before-blocks: 0 - space-before-function-paren: 0 - space-before-keywords: 0 - space-in-parens: 0 - space-infix-ops: 0 - space-return-throw-case: 0 - space-unary-ops: 0 - spaced-comment: 0 - wrap-regex: 0 - - # ECMAScript 6 - arrow-body-style: 0 - arrow-parens: 0 - arrow-spacing: 0 - constructor-super: 0 - generator-star-spacing: 0 - no-arrow-condition: 0 - no-class-assign: 0 - no-const-assign: 0 - no-dupe-class-members: 0 - no-this-before-super: 0 - no-var: 0 - object-shorthand: 0 - prefer-arrow-callback: 0 - prefer-const: 0 - prefer-reflect: 0 - prefer-spread: 0 - prefer-template: 0 - require-yield: 0 diff --git a/_development/.jshintrc b/_development/.jshintrc deleted file mode 100644 index e6395a276a..0000000000 --- a/_development/.jshintrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "undef": true, - "unused": false, - "strict": true, - "browser": true, - "jquery": true, - "devel": true, - "globals": [ - "language", - "token", - "currencyCode", - "$", - "token", - "accountID", - "billID", - "currentMonthName", - "previousMonthName", - "nextMonthName", - "everything", - "moment" - ] -} \ No newline at end of file diff --git a/_development/codesniffer/ruleset.xml b/_development/codesniffer/ruleset.xml deleted file mode 100644 index 5a6a92bb44..0000000000 --- a/_development/codesniffer/ruleset.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/_development/codestyle.xml b/_development/codestyle.xml deleted file mode 100644 index 1429217c85..0000000000 --- a/_development/codestyle.xml +++ /dev/null @@ -1,322 +0,0 @@ - - diff --git a/_development/cover.sh b/_development/cover.sh deleted file mode 100755 index 6807f88c0a..0000000000 --- a/_development/cover.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -# set testing environment -cp .env.testing .env - -# set cover: -cp phpunit.cover.xml phpunit.xml - -# delete test databases: -if [ -f storage/database/testing.db ] -then - echo "Will not remove test db" - # rm storage/database/testing.db -fi - -if [ -f storage/database/testing-copy.db ] -then - echo "Will not remove test db" - # rm storage/database/testing-copy.db -fi - -# test! -if [ -z "$1" ] -then - echo "Running all tests..." - phpunit -fi - -# test selective.. -dirs=("acceptance/Controllers" "acceptance/Controllers/Auth" "acceptance/Controllers/Chart" "unit") -# -if [ ! -z "$1" ] -then - for i in "${dirs[@]}" - do - firstFile="./tests/$i/$1.php" - secondFile="./tests/$i/$1Test.php" - if [ -f "$firstFile" ] - then - # run it! - echo "Now running $firstFile" - phpunit $firstFile - result=$? - fi - if [ -f "$secondFile" ] - then - # run it! - echo "Now running $secondFile" - phpunit $secondFile - result=$? - fi - - - done -fi - -# restore .env file -cp .env.local .env - -# restore cover -cp phpunit.default.xml phpunit.xml - -exit ${result} \ No newline at end of file diff --git a/_development/favicon.pxm b/_development/favicon.pxm deleted file mode 100644 index 18998b1f1c967132475f208afa3b4b0979528322..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102164 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 diff --git a/_development/gulpfile.js b/_development/gulpfile.js deleted file mode 100644 index dc6f1ebb4e..0000000000 --- a/_development/gulpfile.js +++ /dev/null @@ -1,16 +0,0 @@ -var elixir = require('laravel-elixir'); - -/* - |-------------------------------------------------------------------------- - | Elixir Asset Management - |-------------------------------------------------------------------------- - | - | Elixir provides a clean, fluent API for defining some basic Gulp tasks - | for your Laravel application. By default, we are compiling the Sass - | file for our application, as well as publishing vendor resources. - | - */ - -elixir(function(mix) { - mix.sass('app.scss'); -}); diff --git a/_development/phpmd/phpmd.xml b/_development/phpmd/phpmd.xml deleted file mode 100644 index 1e24a5344d..0000000000 --- a/_development/phpmd/phpmd.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - Bla bla - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/_development/phpspec.yml b/_development/phpspec.yml deleted file mode 100644 index 1f6a86a520..0000000000 --- a/_development/phpspec.yml +++ /dev/null @@ -1,5 +0,0 @@ -suites: - main: - namespace: FireflyIII - psr4_prefix: FireflyIII - src_path: app diff --git a/_development/phpunit.cover.xml b/_development/phpunit.cover.xml deleted file mode 100644 index 08ba731694..0000000000 --- a/_development/phpunit.cover.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - ./tests/ - - - - - app/Http - - - vendor/ - app/Console - app/Events - app/Exceptions - app/Generator - app/Handlers - app/Helpers - - app/Jobs - app/Listeners - app/Models - app/Policies - app/Providers - app/Repositories - app/Rules - app/Sql - app/Support - app/Validation - - - - - - - - - - - - - - - diff --git a/_development/phpunit.default.xml b/_development/phpunit.default.xml deleted file mode 100644 index 31ac89bcfe..0000000000 --- a/_development/phpunit.default.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - ./tests/ - - - - - app/ - - - - - - - - - - - - - diff --git a/_development/phpunit.xml b/_development/phpunit.xml deleted file mode 100644 index 31ac89bcfe..0000000000 --- a/_development/phpunit.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - ./tests/ - - - - - app/ - - - - - - - - - - - - - diff --git a/_development/pu.sh b/_development/pu.sh deleted file mode 100755 index e368f8b520..0000000000 --- a/_development/pu.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash - -searchFile="" -deleteDatabases=false - -while getopts ":nhf:" opt; do - case $opt in - n) - # echo "-n was triggered: new database!" >&2 - deleteDatabases=true - ;; - f) - #echo "-f was triggered: file! $OPTARG" >&2 - searchFile=$OPTARG - ;; - h) - echo "n: new database" >&2 - echo "f: which file to run" >&2 - ;; - :) - echo "Option -$OPTARG requires an argument." >&2 - exit 1 - ;; - \?) - echo "Invalid option: -$OPTARG" >&2 - exit 1 - ;; - esac -done - -# set testing environment -cp .env.testing .env - -# set default phpunit. -cp phpunit.default.xml phpunit.xml - -# "create" default attachment: -touch storage/upload/at-1.data -touch storage/upload/at-2.data - -# delete databses: -if [ "$deleteDatabases" = true ] ; then - echo "Will delete and recreate the databases." - - # delete test database: - if [ -f storage/database/testing.db ] - then - echo "Deleted testing.db" - rm storage/database/testing.db - fi - - # delete test database copy: - if [ -f storage/database/testing-copy.db ] - then - echo "Delete testing-copy.db" - rm storage/database/testing-copy.db - fi -fi - -# do not delete database: -if [ "$deleteDatabases" = false ] ; then - echo "Will not delete databases." -fi - -# test! -if [ "$searchFile" == "" ] -then - echo "Running all tests..." - phpunit - result=$? -fi - -# test selective.. -dirs=("acceptance/Controllers" "acceptance/Controllers/Auth" "acceptance/Controllers/Chart" "unit") -# -if [ "$searchFile" != "" ] -then - echo "Will run test for '$searchFile'" - for i in "${dirs[@]}" - do - firstFile="./tests/$i/$searchFile.php" - secondFile="./tests/$i/"$searchFile"Test.php" - if [ -f "$firstFile" ] - then - # run it! - echo "Found file '$firstFile'" - phpunit --verbose $firstFile - result=$? - fi - if [ -f "$secondFile" ] - then - # run it! - echo "Found file '$secondFile'" - phpunit --verbose $secondFile - result=$? - fi - done -fi - -# restore .env file -cp .env.local .env - -exit ${result} \ No newline at end of file diff --git a/_development/readme.txt b/_development/readme.txt deleted file mode 100644 index f80cca7765..0000000000 --- a/_development/readme.txt +++ /dev/null @@ -1 +0,0 @@ -These are some of the files I use for code formatting, PHPMD and PHPCS. diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 3920a95155..5233e5afeb 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -250,8 +250,8 @@ class AccountController extends Controller if ($cache->has()) { - //$entries = $cache->get(); - //return view('categories.show', compact('category', 'journals', 'entries', 'hideCategory', 'subTitle')); + $entries = $cache->get(); + return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index d06193e5ea..8dc927cb48 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -53,7 +53,7 @@ class AccountController extends Controller $cache->addProperty('expenseAccounts'); $cache->addProperty('accounts'); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } $accounts = $repository->getAccountsByType(['Expense account', 'Beneficiary account']); @@ -105,7 +105,7 @@ class AccountController extends Controller $cache->addProperty('frontpage'); $cache->addProperty('accounts'); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } $frontPage = Preferences::get('frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray()); @@ -151,7 +151,7 @@ class AccountController extends Controller $cache->addProperty('default'); $cache->addProperty($accounts); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } foreach ($accounts as $account) { @@ -196,7 +196,7 @@ class AccountController extends Controller $cache->addProperty('single'); $cache->addProperty($account->id); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } $format = (string)trans('config.month_and_day'); diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index b763da7ac2..c2b9675ca5 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -308,7 +308,7 @@ class CategoryController extends Controller $cache->addProperty('specific-period'); if ($cache->has()) { - // return $cache->get(); + return $cache->get(); } $entries = new Collection; Log::debug('Start is ' . $start . ' en end is ' . $end); diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index c6c24886dc..bbbf50eb58 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -100,7 +100,7 @@ class ReportController extends Controller $cache->addProperty($accounts); $cache->addProperty($end); if ($cache->has()) { - // return Response::json($cache->get()); + return Response::json($cache->get()); } // always per month. From ec2027b8db401d5dbd351aa9358cde3f8919773a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 14:48:52 +0200 Subject: [PATCH 134/206] Update git ignore. [skip ci] --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8edc368963..736f45d8ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /vendor /node_modules .env - +_development .env.local From f373c726798f4640de8489ff4972031a159a0faf Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 15:01:29 +0200 Subject: [PATCH 135/206] Remove tests. --- app/Support/Migration/TestData.php | 153 +++++------ database/seeds/TestDataSeeder.php | 10 +- tests/TestCase.php | 79 ------ .../Controllers/AccountControllerTest.php | 139 ---------- .../Controllers/AttachmentControllerTest.php | 90 ------- .../Controllers/Auth/AuthControllerTest.php | 81 ------ .../Auth/PasswordControllerTest.php | 27 -- .../Controllers/BillControllerTest.php | 165 ------------ .../Controllers/BudgetControllerTest.php | 196 -------------- .../Controllers/CategoryControllerTest.php | 156 ------------ .../Chart/ChartAccountControllerTest.php | 59 ----- .../Chart/ChartBillControllerTest.php | 38 --- .../Chart/ChartBudgetControllerTest.php | 77 ------ .../Chart/ChartCategoryControllerTest.php | 106 -------- .../Chart/ChartPiggyBankControllerTest.php | 26 -- .../Chart/ChartReportControllerTest.php | 43 ---- .../Controllers/CsvControllerTest.php | 239 ------------------ .../Controllers/CurrencyControllerTest.php | 134 ---------- .../Controllers/HelpControllerTest.php | 39 --- .../Controllers/HomeControllerTest.php | 57 ----- .../Controllers/JsonControllerTest.php | 177 ------------- .../Controllers/NewUserControllerTest.php | 59 ----- .../Controllers/PiggyBankControllerTest.php | 198 --------------- .../Controllers/PreferencesControllerTest.php | 50 ---- .../Controllers/ProfileControllerTest.php | 83 ------ .../Controllers/ReportControllerTest.php | 130 ---------- .../Controllers/RuleControllerTest.php | 177 ------------- .../Controllers/RuleGroupControllerTest.php | 120 --------- .../Controllers/SearchControllerTest.php | 34 --- .../Controllers/TagControllerTest.php | 129 ---------- .../Controllers/TransactionControllerTest.php | 157 ------------ 31 files changed, 86 insertions(+), 3142 deletions(-) delete mode 100644 tests/acceptance/Controllers/AccountControllerTest.php delete mode 100644 tests/acceptance/Controllers/AttachmentControllerTest.php delete mode 100644 tests/acceptance/Controllers/Auth/AuthControllerTest.php delete mode 100644 tests/acceptance/Controllers/Auth/PasswordControllerTest.php delete mode 100644 tests/acceptance/Controllers/BillControllerTest.php delete mode 100644 tests/acceptance/Controllers/BudgetControllerTest.php delete mode 100644 tests/acceptance/Controllers/CategoryControllerTest.php delete mode 100644 tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php delete mode 100644 tests/acceptance/Controllers/Chart/ChartBillControllerTest.php delete mode 100644 tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php delete mode 100644 tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php delete mode 100644 tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php delete mode 100644 tests/acceptance/Controllers/Chart/ChartReportControllerTest.php delete mode 100644 tests/acceptance/Controllers/CsvControllerTest.php delete mode 100644 tests/acceptance/Controllers/CurrencyControllerTest.php delete mode 100644 tests/acceptance/Controllers/HelpControllerTest.php delete mode 100644 tests/acceptance/Controllers/HomeControllerTest.php delete mode 100644 tests/acceptance/Controllers/JsonControllerTest.php delete mode 100644 tests/acceptance/Controllers/NewUserControllerTest.php delete mode 100644 tests/acceptance/Controllers/PiggyBankControllerTest.php delete mode 100644 tests/acceptance/Controllers/PreferencesControllerTest.php delete mode 100644 tests/acceptance/Controllers/ProfileControllerTest.php delete mode 100644 tests/acceptance/Controllers/ReportControllerTest.php delete mode 100644 tests/acceptance/Controllers/RuleControllerTest.php delete mode 100644 tests/acceptance/Controllers/RuleGroupControllerTest.php delete mode 100644 tests/acceptance/Controllers/SearchControllerTest.php delete mode 100644 tests/acceptance/Controllers/TagControllerTest.php delete mode 100644 tests/acceptance/Controllers/TransactionControllerTest.php diff --git a/app/Support/Migration/TestData.php b/app/Support/Migration/TestData.php index 9da28f94a6..b81fc3dd51 100644 --- a/app/Support/Migration/TestData.php +++ b/app/Support/Migration/TestData.php @@ -30,6 +30,9 @@ class TestData /** @var Carbon */ private $start; + /** @var string */ + private $time; + /** * TestData constructor. * @@ -45,6 +48,7 @@ class TestData $this->start = $start; $this->end = $end; + $this->time = $end->format('Y-m-d H:i:s'); } /** @@ -64,8 +68,8 @@ class TestData $insert = []; foreach ($this->data['accounts'] as $account) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $account['user_id'], 'account_type_id' => $account['account_type_id'], 'name' => Crypt::encrypt($account['name']), @@ -79,8 +83,8 @@ class TestData $insert = []; foreach ($this->data['account-meta'] as $meta) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $meta['account_id'], 'name' => $meta['name'], 'data' => $meta['data'], @@ -100,8 +104,8 @@ class TestData $data = Crypt::encrypt($attachment['content']); $attachmentId = DB::table('attachments')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'attachable_id' => $attachment['attachable_id'], 'attachable_type' => $attachment['attachable_type'], 'user_id' => $attachment['user_id'], @@ -128,8 +132,8 @@ class TestData $insert = []; foreach ($this->data['bills'] as $bill) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $bill['user_id'], 'name' => Crypt::encrypt($bill['name']), 'match' => Crypt::encrypt($bill['match']), @@ -155,8 +159,8 @@ class TestData $insert = []; foreach ($this->data['budgets'] as $budget) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $budget['user_id'], 'name' => Crypt::encrypt($budget['name']), 'encrypted' => 1, @@ -168,8 +172,8 @@ class TestData $amount = rand($limit['amount_min'], $limit['amount_max']); $limitId = DB::table('budget_limits')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'budget_id' => $limit['budget_id'], 'startdate' => $limit['startdate'], 'amount' => $amount, @@ -180,8 +184,8 @@ class TestData DB::table('limit_repetitions')->insert( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'budget_limit_id' => $limitId, 'startdate' => $limit['startdate'], 'enddate' => Navigation::endOfPeriod(new Carbon($limit['startdate']), $limit['repeat_freq'])->format('Y-m-d'), @@ -195,8 +199,8 @@ class TestData $amount = rand($limit['amount_min'], $limit['amount_max']); $limitId = DB::table('budget_limits')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'budget_id' => $limit['budget_id'], 'startdate' => $current->format('Y-m-d'), 'amount' => $amount, @@ -207,8 +211,8 @@ class TestData DB::table('limit_repetitions')->insert( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'budget_limit_id' => $limitId, 'startdate' => $current->format('Y-m-d'), 'enddate' => Navigation::endOfPeriod($current, 'monthly')->format('Y-m-d'), @@ -230,8 +234,8 @@ class TestData $insert = []; foreach ($this->data['categories'] as $category) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $category['user_id'], 'name' => Crypt::encrypt($category['name']), 'encrypted' => 1, @@ -256,8 +260,8 @@ class TestData $description = str_replace(':month', $month, $withdrawal['description']); $journalId = DB::table('transaction_journals')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $withdrawal['user_id'], 'transaction_type_id' => 1, 'bill_id' => $withdrawal['bill_id'] ?? null, @@ -275,15 +279,15 @@ class TestData ); $amount = (rand($withdrawal['min_amount'] * 100, $withdrawal['max_amount'] * 100)) / 100; $transactions[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'transaction_journal_id' => $journalId, 'account_id' => $withdrawal['source_id'], 'amount' => $amount * -1, ]; $transactions[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'transaction_journal_id' => $journalId, 'account_id' => $withdrawal['destination_id'], 'amount' => $amount, @@ -316,8 +320,8 @@ class TestData $description = str_replace(':month', $month, $deposit['description']); $journalId = DB::table('transaction_journals')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $deposit['user_id'], 'transaction_type_id' => 2, 'bill_id' => $deposit['bill_id'] ?? null, @@ -335,15 +339,15 @@ class TestData ); $amount = (rand($deposit['min_amount'] * 100, $deposit['max_amount'] * 100)) / 100; $transactions[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'transaction_journal_id' => $journalId, 'account_id' => $deposit['source_id'], 'amount' => $amount * -1, ]; $transactions[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'transaction_journal_id' => $journalId, 'account_id' => $deposit['destination_id'], 'amount' => $amount, @@ -365,8 +369,8 @@ class TestData $description = str_replace(':month', $month, $transfer['description']); $journalId = DB::table('transaction_journals')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $transfer['user_id'], 'transaction_type_id' => 3, 'bill_id' => $transfer['bill_id'] ?? null, @@ -384,15 +388,15 @@ class TestData ); $amount = (rand($transfer['min_amount'] * 100, $transfer['max_amount'] * 100)) / 100; $transactions[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'transaction_journal_id' => $journalId, 'account_id' => $transfer['source_id'], 'amount' => $amount * -1, ]; $transactions[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'transaction_journal_id' => $journalId, 'account_id' => $transfer['destination_id'], 'amount' => $amount, @@ -408,11 +412,12 @@ class TestData ); } } - + DB::table('transactions')->insert($transactions); + $transactions = []; $current->addMonth(); } - DB::table('transactions')->insert($transactions); + } /** @@ -423,8 +428,8 @@ class TestData foreach ($this->data['multi-deposits'] as $deposit) { $journalId = DB::table('transaction_journals')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $deposit['user_id'], 'transaction_type_id' => 2, 'transaction_currency_id' => 1, @@ -444,8 +449,8 @@ class TestData $amount = $deposit['amounts'][$index]; $first = DB::table('transactions')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $deposit['destination_id'], 'transaction_journal_id' => $journalId, 'description' => $description, @@ -454,8 +459,8 @@ class TestData ); $second = DB::table('transactions')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $source, 'transaction_journal_id' => $journalId, 'description' => $description, @@ -487,8 +492,8 @@ class TestData foreach ($this->data['multi-transfers'] as $transfer) { $journalId = DB::table('transaction_journals')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $transfer['user_id'], 'transaction_type_id' => 3, 'transaction_currency_id' => 1, @@ -509,8 +514,8 @@ class TestData $source = $transfer['source_ids'][$index]; $first = DB::table('transactions')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $source, 'transaction_journal_id' => $journalId, 'description' => $description, @@ -519,8 +524,8 @@ class TestData ); $second = DB::table('transactions')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $destination, 'transaction_journal_id' => $journalId, 'description' => $description, @@ -554,8 +559,8 @@ class TestData foreach ($this->data['multi-withdrawals'] as $withdrawal) { $journalId = DB::table('transaction_journals')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $withdrawal['user_id'], 'transaction_type_id' => 1, 'transaction_currency_id' => 1, @@ -575,8 +580,8 @@ class TestData $amount = $withdrawal['amounts'][$index]; $first = DB::table('transactions')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $withdrawal['source_id'], 'transaction_journal_id' => $journalId, 'description' => $description, @@ -585,8 +590,8 @@ class TestData ); $second = DB::table('transactions')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $destination, 'transaction_journal_id' => $journalId, 'description' => $description, @@ -635,8 +640,8 @@ class TestData foreach ($this->data['piggy-banks'] as $piggyBank) { $piggyId = DB::table('piggy_banks')->insertGetId( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'account_id' => $piggyBank['account_id'], 'name' => Crypt::encrypt($piggyBank['name']), 'targetamount' => $piggyBank['targetamount'], @@ -650,8 +655,8 @@ class TestData if (isset($piggyBank['currentamount'])) { DB::table('piggy_bank_repetitions')->insert( [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'piggy_bank_id' => $piggyId, 'startdate' => $piggyBank['startdate'], 'currentamount' => $piggyBank['currentamount'], @@ -669,8 +674,8 @@ class TestData $insert = []; foreach ($this->data['rule-groups'] as $group) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $group['user_id'], 'order' => $group['order'], 'title' => $group['title'], @@ -682,8 +687,8 @@ class TestData $insert = []; foreach ($this->data['rules'] as $rule) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $rule['user_id'], 'rule_group_id' => $rule['rule_group_id'], 'order' => $rule['order'], @@ -698,8 +703,8 @@ class TestData $insert = []; foreach ($this->data['rule-triggers'] as $trigger) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'rule_id' => $trigger['rule_id'], 'order' => $trigger['order'], 'active' => $trigger['active'], @@ -713,8 +718,8 @@ class TestData $insert = []; foreach ($this->data['rule-actions'] as $action) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'rule_id' => $action['rule_id'], 'order' => $action['order'], 'active' => $action['active'], @@ -735,8 +740,8 @@ class TestData foreach ($this->data['tags'] as $tag) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'user_id' => $tag['user_id'], 'tag' => Crypt::encrypt($tag['tag']), 'tagMode' => $tag['tagMode'], @@ -756,8 +761,8 @@ class TestData foreach ($this->data['users'] as $user) { $insert[] = [ - 'created_at' => DB::raw('NOW()'), - 'updated_at' => DB::raw('NOW()'), + 'created_at' => $this->time, + 'updated_at' => $this->time, 'email' => $user['email'], 'password' => bcrypt($user['password']), ]; diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index c0f18618c6..deb01e6226 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -8,7 +8,6 @@ declare(strict_types = 1); * of the MIT license. See the LICENSE file for details. */ -use Carbon\Carbon; use FireflyIII\Support\Migration\TestData; use Illuminate\Database\Seeder; @@ -31,13 +30,18 @@ class TestDataSeeder extends Seeder */ public function run() { - $disk = Storage::disk('database'); - $env = App::environment(); + $disk = Storage::disk('database'); + $env = App::environment(); + Log::debug('Environment is ' . $env); $fileName = 'seed.' . $env . '.json'; if ($disk->exists($fileName)) { + Log::debug('Now seeding ' . $fileName); $file = json_decode($disk->get($fileName), true); // run the file: TestData::run($file); + + return; } + Log::info('No seed file (' . $fileName . ') for environment ' . $env); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 6c0953cfab..87ab664e16 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -15,34 +15,6 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase */ protected $baseUrl = 'http://localhost'; - /** - * @param User $user - * @param string $range - */ - public function changeDateRange(User $user, $range) - { - $valid = ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom']; - if (in_array($range, $valid)) { - Preference::where('user_id', $user->id)->where('name', 'viewRange')->delete(); - Preference::create( - [ - 'user_id' => $user->id, - 'name' => 'viewRange', - 'data' => $range, - ] - ); - // set period to match? - - } - if ($range === 'custom') { - $this->session( - [ - 'start' => Carbon::now()->subDays(20), - 'end' => Carbon::now(), - ] - ); - } - } /** * Creates the application. @@ -58,30 +30,6 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase return $app; } - /** - * @return array - */ - public function dateRangeProvider() - { - return [ - 'one day' => ['1D'], - 'one week' => ['1W'], - 'one month' => ['1M'], - 'three months' => ['3M'], - 'six months' => ['6M'], - 'one year' => ['1Y'], - 'custom range' => ['custom'], - ]; - } - - /** - * @return User - */ - public function emptyUser() - { - return User::find(2); - } - /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. @@ -111,16 +59,6 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase } // if the database copy does exists, copy back as original. - - $this->session( - [ - 'start' => Carbon::now()->startOfMonth(), - 'end' => Carbon::now()->endOfMonth(), - 'first' => Carbon::now()->startOfYear(), - ] - ); - - } /** @@ -132,23 +70,6 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase parent::tearDown(); } - /** - * @return User - */ - public function toBeDeletedUser() - { - return User::find(3); - } - - /** - * @return User - */ - public function user() - { - $user = User::find(1); - - return $user; - } /** * @param string $class diff --git a/tests/acceptance/Controllers/AccountControllerTest.php b/tests/acceptance/Controllers/AccountControllerTest.php deleted file mode 100644 index 1b42665974..0000000000 --- a/tests/acceptance/Controllers/AccountControllerTest.php +++ /dev/null @@ -1,139 +0,0 @@ -be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/accounts/create/asset'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/accounts/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::destroy - */ - public function testDestroy() - { - $this->be($this->user()); - $this->session(['accounts.delete.url' => 'http://localhost']); - $this->call('POST', '/accounts/destroy/6'); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/accounts/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::index - * @covers FireflyIII\Http\Controllers\AccountController::isInArray - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/accounts/asset'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::show - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShow($range) - { - $repository = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface'); - $repository->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); - - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/accounts/show/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::store - * @covers FireflyIII\Http\Requests\AccountFormRequest::authorize - * @covers FireflyIII\Http\Requests\AccountFormRequest::rules - * - */ - public function testStore() - { - $this->be($this->user()); - $this->session(['accounts.create.url' => 'http://localhost']); - $args = [ - 'name' => 'Some kind of test account.', - 'what' => 'asset', - 'amount_currency_id_virtualBalance' => 1, - 'amount_currency_id_openingBalance' => 1, - ]; - - $this->call('POST', '/accounts/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - - } - - /** - * @covers FireflyIII\Http\Controllers\AccountController::update - * @covers FireflyIII\Http\Requests\AccountFormRequest::authorize - * @covers FireflyIII\Http\Requests\AccountFormRequest::rules - */ - public function testUpdate() - { - $this->session(['accounts.edit.url' => 'http://localhost']); - $args = [ - 'id' => 1, - 'name' => 'TestData new name', - 'active' => 1, - ]; - $this->be($this->user()); - - $this->call('POST', '/accounts/update/1', $args); - $this->assertResponseStatus(302); - - $this->assertSessionHas('success'); - - } -} diff --git a/tests/acceptance/Controllers/AttachmentControllerTest.php b/tests/acceptance/Controllers/AttachmentControllerTest.php deleted file mode 100644 index 4d9331c34a..0000000000 --- a/tests/acceptance/Controllers/AttachmentControllerTest.php +++ /dev/null @@ -1,90 +0,0 @@ -be($this->user()); - $this->call('GET', '/attachment/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AttachmentController::destroy - */ - public function testDestroy() - { - $this->be($this->user()); - $this->session(['attachments.delete.url' => 'http://localhost']); - $this->call('POST', '/attachment/destroy/2'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\AttachmentController::download - */ - public function testDownload() - { - $this->be($this->user()); - $this->call('GET', '/attachment/download/1'); - $this->assertResponseStatus(200); - // must have certain headers - - } - - /** - * @covers FireflyIII\Http\Controllers\AttachmentController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/attachment/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AttachmentController::preview - */ - public function testPreview() - { - $this->be($this->user()); - $this->call('GET', '/attachment/preview/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\AttachmentController::update - * @covers FireflyIII\Http\Requests\AttachmentFormRequest::authorize - * @covers FireflyIII\Http\Requests\AttachmentFormRequest::rules - */ - public function testUpdate() - { - $this->session(['attachments.edit.url' => 'http://localhost']); - - $args = [ - 'title' => 'New title', - 'description' => 'New descr', - 'notes' => 'New notes', - ]; - $this->be($this->user()); - - $this->call('POST', '/attachment/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/Auth/AuthControllerTest.php b/tests/acceptance/Controllers/Auth/AuthControllerTest.php deleted file mode 100644 index affaf9b106..0000000000 --- a/tests/acceptance/Controllers/Auth/AuthControllerTest.php +++ /dev/null @@ -1,81 +0,0 @@ -be($this->user()); - $this->call('GET', '/logout'); - $this->assertResponseStatus(302); - - // index should now redirect: - $this->call('GET', '/'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::login - * @covers FireflyIII\Http\Controllers\Auth\AuthController::__construct - * @covers FireflyIII\Http\Controllers\Auth\AuthController::sendFailedLoginResponse - * @covers FireflyIII\Http\Controllers\Auth\AuthController::getFailedLoginMessage - * - */ - public function testLogin() - { - $args = [ - 'email' => 'thegrumpydictator@gmail.com', - 'password' => 'james', - 'remember' => 1, - ]; - $this->call('POST', '/login', $args); - $this->assertResponseStatus(302); - - $this->call('GET', '/'); - $this->assertResponseStatus(200); - - - } - - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::register - * @covers FireflyIII\Http\Controllers\Auth\AuthController::create - * @covers FireflyIII\Http\Controllers\Auth\AuthController::isBlockedDomain - * @covers FireflyIII\Http\Controllers\Auth\AuthController::getBlockedDomains - * @covers FireflyIII\Http\Controllers\Auth\AuthController::validator - */ - public function testRegister() - { - $args = [ - 'email' => 'thegrumpydictator+test@gmail.com', - 'password' => 'james123', - 'password_confirmation' => 'james123', - ]; - $this->call('POST', '/register', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('start'); - } - - /** - * @covers FireflyIII\Http\Controllers\Auth\AuthController::showRegistrationForm - */ - public function testShowRegistrationForm() - { - $this->call('GET', '/register'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/Auth/PasswordControllerTest.php b/tests/acceptance/Controllers/Auth/PasswordControllerTest.php deleted file mode 100644 index 50da8032b7..0000000000 --- a/tests/acceptance/Controllers/Auth/PasswordControllerTest.php +++ /dev/null @@ -1,27 +0,0 @@ - 'thegrumpydictator@gmail.com', - ]; - $this->call('POST', '/password/email', $args); - $this->assertResponseStatus(302); - } - -} diff --git a/tests/acceptance/Controllers/BillControllerTest.php b/tests/acceptance/Controllers/BillControllerTest.php deleted file mode 100644 index 42ac6bafa6..0000000000 --- a/tests/acceptance/Controllers/BillControllerTest.php +++ /dev/null @@ -1,165 +0,0 @@ -be($this->user()); - $this->call('GET', '/bills/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/bills/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::destroy - */ - public function testDestroy() - { - $this->session(['bills.delete.url' => 'http://localhost']); - $this->be($this->user()); - $this->call('POST', '/bills/destroy/2'); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/bills/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::index - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/bills'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::rescan - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testRescan($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/bills/rescan/1'); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::show - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShow($range) - { - $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); - $repository->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); - $repository->shouldReceive('nextExpectedMatch')->once()->andReturn(new Carbon); - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/bills/show/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::store - * @covers FireflyIII\Http\Requests\BillFormRequest::authorize - * @covers FireflyIII\Http\Requests\BillFormRequest::getBillData - * @covers FireflyIII\Http\Requests\BillFormRequest::rules - */ - public function testStore() - { - $this->session(['bills.create.url' => 'http://localhost']); - $args = [ - 'name' => 'Some test', - 'match' => 'words', - 'amount_min' => 10, - 'amount_max' => 100, - 'amount_currency_id_amount_min' => 1, - 'amount_currency_id_amount_max' => 1, - 'date' => '20160101', - 'repeat_freq' => 'monthly', - 'skip' => 0, - ]; - - $this->be($this->user()); - $this->call('POST', '/bills/store', $args); - $this->assertSessionHas('success'); - - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\BillController::update - * @covers FireflyIII\Http\Requests\BillFormRequest::authorize - * @covers FireflyIII\Http\Requests\BillFormRequest::getBillData - * @covers FireflyIII\Http\Requests\BillFormRequest::rules - */ - public function testUpdate() - { - $this->session(['bills.edit.url' => 'http://localhost']); - $args = [ - 'id' => 1, - 'name' => 'Some test', - 'match' => 'words', - 'amount_min' => 10, - 'amount_max' => 100, - 'amount_currency_id_amount_min' => 1, - 'amount_currency_id_amount_max' => 1, - 'date' => '20160101', - 'repeat_freq' => 'monthly', - 'skip' => 0, - ]; - - $this->be($this->user()); - $this->call('POST', '/bills/update/1', $args); - $this->assertSessionHas('success'); - - $this->assertResponseStatus(302); - } -} diff --git a/tests/acceptance/Controllers/BudgetControllerTest.php b/tests/acceptance/Controllers/BudgetControllerTest.php deleted file mode 100644 index 2793af22b8..0000000000 --- a/tests/acceptance/Controllers/BudgetControllerTest.php +++ /dev/null @@ -1,196 +0,0 @@ - 1200, - ]; - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - - $this->call('POST', '/budgets/amount/1', $args); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::create - */ - public function testCreate() - { - $this->be($this->user()); - $this->call('GET', '/budgets/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/budgets/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::destroy - */ - public function testDestroy() - { - $this->be($this->user()); - - $this->session(['budgets.delete.url' => 'http://localhost']); - $this->call('POST', '/budgets/destroy/2'); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/budgets/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::index - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/budgets'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::noBudget - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testNoBudget($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/budgets/list/noBudget'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::postUpdateIncome - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testPostUpdateIncome($range) - { - $args = [ - 'amount' => 1200, - ]; - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - - $this->call('POST', '/budgets/income', $args); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::show - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShow($range) - { - // mock some stuff: - $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $repository->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); - $repository->shouldReceive('firstActivity')->once()->andReturn(new Carbon); - $repository->shouldReceive('spentPerDay')->once()->andReturn([]); - - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/budgets/show/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::store - * @covers FireflyIII\Http\Requests\BudgetFormRequest::authorize - * @covers FireflyIII\Http\Requests\BudgetFormRequest::rules - */ - public function testStore() - { - $this->be($this->user()); - $this->session(['budgets.create.url' => 'http://localhost']); - $args = [ - 'name' => 'Some kind of test budget.', - ]; - - $this->call('POST', '/budgets/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::update - * @covers FireflyIII\Http\Requests\BudgetFormRequest::authorize - * @covers FireflyIII\Http\Requests\BudgetFormRequest::rules - */ - public function testUpdate() - { - $this->be($this->user()); - $this->session(['budgets.edit.url' => 'http://localhost']); - $args = [ - 'name' => 'Some kind of test budget.', - 'id' => 1, - ]; - - $this->call('POST', '/budgets/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\BudgetController::updateIncome - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testUpdateIncome($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/budgets/income'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/CategoryControllerTest.php b/tests/acceptance/Controllers/CategoryControllerTest.php deleted file mode 100644 index 80a4a6f4f9..0000000000 --- a/tests/acceptance/Controllers/CategoryControllerTest.php +++ /dev/null @@ -1,156 +0,0 @@ -be($this->user()); - $this->call('GET', '/categories/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/categories/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::destroy - */ - public function testDestroy() - { - $this->be($this->user()); - - $this->session(['categories.delete.url' => 'http://localhost']); - - $this->call('POST', '/categories/destroy/2'); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/categories/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::index - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/categories'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::noCategory - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testNoCategory($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/categories/list/noCategory'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::show - * @covers FireflyIII\Http\Controllers\Controller::getSumOfRange - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShow($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/categories/show/1'); - $this->assertResponseStatus(200); - - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::showWithDate - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShowWithDate($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/categories/show/1/20150101'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::store - * @covers FireflyIII\Http\Requests\CategoryFormRequest::authorize - * @covers FireflyIII\Http\Requests\CategoryFormRequest::rules - */ - public function testStore() - { - $this->be($this->user()); - $this->session(['categories.create.url' => 'http://localhost']); - $args = [ - 'name' => 'Some kind of test cat.', - ]; - - $this->call('POST', '/categories/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\CategoryController::update - * @covers FireflyIII\Http\Requests\CategoryFormRequest::authorize - * @covers FireflyIII\Http\Requests\CategoryFormRequest::rules - */ - public function testUpdate() - { - $this->be($this->user()); - $this->session(['categories.edit.url' => 'http://localhost']); - $args = [ - 'name' => 'Some kind of test category.', - 'id' => 1, - ]; - - $this->call('POST', '/categories/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php b/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php deleted file mode 100644 index bd419a1932..0000000000 --- a/tests/acceptance/Controllers/Chart/ChartAccountControllerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -be($this->user()); - $this->call('GET', '/chart/account/expense'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\AccountController::frontpage - */ - public function testFrontpage() - { - $this->be($this->user()); - $this->call('GET', '/chart/account/frontpage'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\AccountController::report - */ - public function testReport() - { - $this->be($this->user()); - $this->call('GET', '/chart/account/report/default/20160101/20160131/1'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\AccountController::single - */ - public function testSingle() - { - $this->be($this->user()); - $this->call('GET', '/chart/account/1'); - - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php b/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php deleted file mode 100644 index 2f3011a714..0000000000 --- a/tests/acceptance/Controllers/Chart/ChartBillControllerTest.php +++ /dev/null @@ -1,38 +0,0 @@ -be($this->user()); - $this->call('GET', '/chart/bill/frontpage'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\BillController::single - */ - public function testSingle() - { - $this->be($this->user()); - $this->call('GET', '/chart/bill/1'); - - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php b/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php deleted file mode 100644 index becb98cdcc..0000000000 --- a/tests/acceptance/Controllers/Chart/ChartBudgetControllerTest.php +++ /dev/null @@ -1,77 +0,0 @@ -mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $repository->shouldReceive('spentPerDay')->once()->andReturn([]); - $repository->shouldReceive('getFirstBudgetLimitDate')->once()->andReturn(new Carbon); - - $this->be($this->user()); - $this->call('GET', '/chart/budget/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\BudgetController::budgetLimit - */ - public function testBudgetLimit() - { - $this->be($this->user()); - $this->call('GET', '/chart/budget/1/1'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\BudgetController::frontpage - */ - public function testFrontpage() - { - $this->be($this->user()); - $this->call('GET', '/chart/budget/frontpage'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\BudgetController::multiYear - */ - public function testMultiYear() - { - - $budget = new Budget; - $budget->id = 1; - $budget->dateFormatted = '2015'; - $budget->budgeted = 120; - - $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $repository->shouldReceive('getBudgetedPerYear')->once()->andReturn(new Collection([$budget])); - $repository->shouldReceive('getBudgetsAndExpensesPerYear')->once()->andReturn([]); - - $this->be($this->user()); - $this->call('GET', '/chart/budget/multi-year/default/20150101/20160101/1/1'); - $this->assertResponseStatus(200); - - } -} diff --git a/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php b/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php deleted file mode 100644 index b3fc910f42..0000000000 --- a/tests/acceptance/Controllers/Chart/ChartCategoryControllerTest.php +++ /dev/null @@ -1,106 +0,0 @@ -be($this->user()); - $this->call('GET', '/chart/category/1/all'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\CategoryController::currentPeriod - */ - public function testCurrentPeriod() - { - $this->be($this->user()); - $this->call('GET', '/chart/category/1/period'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\CategoryController::earnedInPeriod - */ - public function testEarnedInPeriod() - { - $repository = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); - $repository->shouldReceive('earnedForAccountsPerMonth')->once()->andReturn(new Collection); - - $this->be($this->user()); - $this->call('GET', '/chart/category/earned-in-period/default/20150101/20151231/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\CategoryController::frontpage - */ - public function testFrontpage() - { - $repository = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); - $repository->shouldReceive('spentForAccountsPerMonth')->once()->andReturn(new Collection); - $repository->shouldReceive('sumSpentNoCategory')->once()->andReturn('120'); - - $this->be($this->user()); - $this->call('GET', '/chart/category/frontpage'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\CategoryController::multiYear - */ - public function testMultiYear() - { - $repository = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); - $repository->shouldReceive('listMultiYear')->once()->andReturn(new Collection); - - $this->be($this->user()); - $this->call('GET', '/chart/category/multi-year/default/20150101/20151231/1/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\CategoryController::specificPeriod - */ - public function testSpecificPeriod() - { - $this->be($this->user()); - $this->call('GET', '/chart/category/1/period/20150101'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\CategoryController::spentInPeriod - */ - public function testSpentInPeriod() - { - - - $repository = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); - $repository->shouldReceive('spentForAccountsPerMonth')->once()->andReturn(new Collection); - - $this->be($this->user()); - $this->call('GET', '/chart/category/spent-in-period/default/20150101/20151231/1'); - $this->assertResponseStatus(200); - - } -} diff --git a/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php b/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php deleted file mode 100644 index 1866039349..0000000000 --- a/tests/acceptance/Controllers/Chart/ChartPiggyBankControllerTest.php +++ /dev/null @@ -1,26 +0,0 @@ -be($this->user()); - $this->call('GET', '/chart/piggy-bank/1'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php b/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php deleted file mode 100644 index fb9164ffab..0000000000 --- a/tests/acceptance/Controllers/Chart/ChartReportControllerTest.php +++ /dev/null @@ -1,43 +0,0 @@ -mock('FireflyIII\Helpers\Report\ReportQueryInterface'); - $repository->shouldReceive('spentPerMonth')->once()->andReturn([]); - $repository->shouldReceive('earnedPerMonth')->once()->andReturn([]); - - $this->be($this->user()); - $this->call('GET', '/chart/report/in-out/default/20150101/20151231/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\Chart\ReportController::yearInOutSummarized - */ - public function testYearInOutSummarized() - { - $repository = $this->mock('FireflyIII\Helpers\Report\ReportQueryInterface'); - $repository->shouldReceive('spentPerMonth')->once()->andReturn([]); - $repository->shouldReceive('earnedPerMonth')->once()->andReturn([]); - - $this->be($this->user()); - $this->call('GET', '/chart/report/in-out-sum/default/20150101/20151231/1'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/CsvControllerTest.php b/tests/acceptance/Controllers/CsvControllerTest.php deleted file mode 100644 index 433b52a167..0000000000 --- a/tests/acceptance/Controllers/CsvControllerTest.php +++ /dev/null @@ -1,239 +0,0 @@ -be($this->user()); - - // mock wizard - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-import-account', 'csv-specifix', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - // mock Data and Reader - $reader = $this->mock('League\Csv\Reader'); - $data = $this->mock('FireflyIII\Helpers\Csv\Data'); - $data->shouldReceive('getReader')->times(2)->andReturn($reader); - $reader->shouldReceive('fetchOne')->withNoArgs()->once()->andReturn([1, 2, 3, 4]); - $reader->shouldReceive('fetchOne')->with(1)->once()->andReturn([1, 2, 3, 4]); - - $data->shouldReceive('getRoles')->once()->andReturn([]); - $data->shouldReceive('getMap')->once()->andReturn([]); - $data->shouldReceive('hasHeaders')->once()->andReturn(false); - - $this->call('GET', '/csv/column_roles'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::downloadConfig - */ - public function testDownloadConfig() - { - $this->be($this->user()); - - $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - $this->call('GET', '/csv/download-config'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::downloadConfigPage - */ - public function testDownloadConfigPage() - { - $this->be($this->user()); - - $fields = ['csv-date-format', 'csv-has-headers', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - $this->call('GET', '/csv/download'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::index - */ - public function testIndex() - { - - $this->be($this->user()); - $this->call('GET', '/csv'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::initialParse - */ - public function testInitialParse() - { - $this->be($this->user()); - - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - $wizard->shouldReceive('processSelectedRoles')->once()->with([])->andReturn([1, 2, 3]); - $wizard->shouldReceive('processSelectedMapping')->once()->with([1, 2, 3], [])->andReturn([1, 2, 3]); - - $this->call('POST', '/csv/initial_parse'); - // should be redirect - $this->assertResponseStatus(302); - - // should be redirected to mapping: - $this->assertRedirectedToRoute('csv.map'); - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::initialParse - */ - public function testInitialParseNoMap() - { - $this->be($this->user()); - - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - $wizard->shouldReceive('processSelectedRoles')->once()->with([])->andReturn([1, 2, 3]); - $wizard->shouldReceive('processSelectedMapping')->once()->with([1, 2, 3], [])->andReturn([]); - - $this->call('POST', '/csv/initial_parse'); - // should be redirect - $this->assertResponseStatus(302); - - // should be redirected to download config: - $this->assertRedirectedToRoute('csv.download-config-page'); - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::map - */ - public function testMap() - { - $this->be($this->user()); - - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - $wizard->shouldReceive('showOptions')->once()->with([])->andReturn([]); - - // mock Data and Reader - $reader = $this->mock('League\Csv\Reader'); - $data = $this->mock('FireflyIII\Helpers\Csv\Data'); - $data->shouldReceive('getReader')->once()->andReturn($reader); - $data->shouldReceive('getMap')->times(3)->andReturn([]); - $data->shouldReceive('hasHeaders')->once()->andReturn(true); - - $wizard->shouldReceive('getMappableValues')->with($reader, [], true)->andReturn([]); - - $data->shouldReceive('getMapped')->once()->andReturn([]); - - - $this->call('GET', '/csv/map'); - $this->assertResponseStatus(200); - - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::process - */ - public function testProcess() - { - $this->be($this->user()); - - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-mapped', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - // mock - $data = $this->mock('FireflyIII\Helpers\Csv\Data'); - - // mock - $importer = $this->mock('FireflyIII\Helpers\Csv\Importer'); - $importer->shouldReceive('setData')->once()->with($data); - $importer->shouldReceive('run')->once()->withNoArgs(); - - $importer->shouldReceive('getRows')->once()->withNoArgs()->andReturn(0); - $importer->shouldReceive('getErrors')->once()->withNoArgs()->andReturn([]); - $importer->shouldReceive('getImported')->once()->withNoArgs()->andReturn(0); - $importer->shouldReceive('getJournals')->once()->withNoArgs()->andReturn(new Collection); - - $this->call('GET', '/csv/process'); - $this->assertResponseStatus(200); - } - - - /** - * @covers FireflyIII\Http\Controllers\CsvController::saveMapping - */ - public function testSaveMapping() - { - $this->be($this->user()); - - $fields = ['csv-file', 'csv-date-format', 'csv-has-headers', 'csv-map', 'csv-roles', 'csv-delimiter']; - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('sessionHasValues')->once()->with($fields)->andReturn(true); - - $this->call('POST', '/csv/save_mapping', ['mapping' => []]); - - $this->assertResponseStatus(302); - $this->assertRedirectedToRoute('csv.download-config-page'); - - } - - /** - * @covers FireflyIII\Http\Controllers\CsvController::upload - */ - public function testUpload() - { - $this->be($this->user()); - - $wizard = $this->mock('FireflyIII\Helpers\Csv\WizardInterface'); - $wizard->shouldReceive('storeCsvFile')->andReturn(''); - - - // mock Data and Reader - $data = $this->mock('FireflyIII\Helpers\Csv\Data'); - $data->shouldReceive('setCsvFileLocation')->once()->with(''); - $data->shouldReceive('setDateFormat')->once()->with('Ymd'); - $data->shouldReceive('setHasHeaders')->once()->with(''); - $data->shouldReceive('setMap')->once()->with([]); - $data->shouldReceive('setMapped')->once()->with([]); - $data->shouldReceive('setRoles')->once()->with([]); - $data->shouldReceive('setSpecifix')->once()->with([]); - $data->shouldReceive('setImportAccount')->once()->with(0); - $data->shouldReceive('setDelimiter')->once()->with(','); - - - $file = new UploadedFile(storage_path('build/test-upload.csv'), 'test-file.csv', 'text/plain', 446); - $this->call('POST', '/csv/upload', ['date_format' => 'Ymd'], [], ['csv' => $file]); - - // csv data set: - $this->assertResponseStatus(302); - } -} diff --git a/tests/acceptance/Controllers/CurrencyControllerTest.php b/tests/acceptance/Controllers/CurrencyControllerTest.php deleted file mode 100644 index 946572f1b9..0000000000 --- a/tests/acceptance/Controllers/CurrencyControllerTest.php +++ /dev/null @@ -1,134 +0,0 @@ -be($this->user()); - $this->call('GET', '/currency/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::defaultCurrency - */ - public function testDefaultCurrency() - { - $this->be($this->user()); - $this->call('GET', '/currency/default/2'); - $this->assertResponseStatus(302); - $this->assertRedirectedToRoute('currency.index'); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/currency/delete/2'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::destroy - */ - public function testDestroy() - { - $this->session(['currency.delete.url' => 'http://localhost/currency']); - $this->be($this->user()); - $this->call('POST', '/currency/destroy/3'); - $this->assertSessionHas('success'); - $this->assertRedirectedToRoute('currency.index'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/currency/edit/2'); - $this->assertResponseStatus(200); - - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::index - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/currency'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::store - * @covers FireflyIII\Http\Requests\CurrencyFormRequest::authorize - * @covers FireflyIII\Http\Requests\CurrencyFormRequest::rules - * @covers FireflyIII\Http\Requests\CurrencyFormRequest::getCurrencyData - */ - public function testStore() - { - $this->be($this->user()); - $this->session(['currency.create.url' => 'http://localhost/currency']); - $args = [ - 'name' => 'New Euro.', - 'symbol' => 'Y', - 'code' => 'IUY', - ]; - - $this->call('POST', '/currency/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - $this->assertRedirectedToRoute('currency.index'); - } - - /** - * @covers FireflyIII\Http\Controllers\CurrencyController::update - * @covers FireflyIII\Http\Requests\CurrencyFormRequest::authorize - * @covers FireflyIII\Http\Requests\CurrencyFormRequest::rules - * @covers FireflyIII\Http\Requests\CurrencyFormRequest::getCurrencyData - */ - public function testUpdate() - { - $this->session(['currency.edit.url' => 'http://localhost/currency']); - - $args = [ - 'id' => 1, - 'name' => 'New Euro.', - 'symbol' => 'Y', - 'code' => 'IUY', - ]; - $this->be($this->user()); - - $this->call('POST', '/currency/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - $this->assertRedirectedToRoute('currency.index'); - - } -} diff --git a/tests/acceptance/Controllers/HelpControllerTest.php b/tests/acceptance/Controllers/HelpControllerTest.php deleted file mode 100644 index 805e815d04..0000000000 --- a/tests/acceptance/Controllers/HelpControllerTest.php +++ /dev/null @@ -1,39 +0,0 @@ -be($this->user()); - $this->call('GET', '/help/index'); - - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\HelpController::show - */ - public function testShowNoRoute() - { - $this->be($this->user()); - $this->call('GET', '/help/indxxex'); - - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/HomeControllerTest.php b/tests/acceptance/Controllers/HomeControllerTest.php deleted file mode 100644 index d2845a39bf..0000000000 --- a/tests/acceptance/Controllers/HomeControllerTest.php +++ /dev/null @@ -1,57 +0,0 @@ -be($this->user()); - - $args = [ - 'start' => '2012-01-01', - 'end' => '2012-04-01', - ]; - - // if date range is > 50, should have flash. - $this->call('POST', '/daterange', $args); - $this->assertResponseStatus(200); - $this->assertSessionHas('warning', '91 days of data may take a while to load.'); - } - - /** - * @covers FireflyIII\Http\Controllers\HomeController::flush - */ - public function testFlush() - { - $this->be($this->user()); - $this->call('GET', '/flush'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\HomeController::index - * @covers FireflyIII\Http\Controllers\Controller::__construct - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/JsonControllerTest.php b/tests/acceptance/Controllers/JsonControllerTest.php deleted file mode 100644 index 27e86491b0..0000000000 --- a/tests/acceptance/Controllers/JsonControllerTest.php +++ /dev/null @@ -1,177 +0,0 @@ -be($this->user()); - $this->call('GET', '/json/action'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::boxBillsPaid - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testBoxBillsPaid($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/json/box/bills-paid'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::boxBillsUnpaid - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testBoxBillsUnpaid($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/json/box/bills-unpaid'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::boxIn - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testBoxIn($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/json/box/in'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::boxOut - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testBoxOut($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/json/box/out'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::categories - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testCategories($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/json/categories'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::endTour - */ - public function testEndTour() - { - $this->be($this->user()); - $response = $this->call('POST', '/json/end-tour'); - $this->assertResponseStatus(200); - $this->assertEquals('"true"', $response->content()); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::expenseAccounts - */ - public function testExpenseAccounts() - { - $this->be($this->user()); - $this->call('GET', '/json/expense-accounts'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::revenueAccounts - */ - public function testRevenueAccounts() - { - $this->be($this->user()); - $this->call('GET', '/json/revenue-accounts'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::tags - */ - public function testTags() - { - $this->be($this->user()); - $this->call('GET', '/json/tags'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::tour - */ - public function testTour() - { - $this->be($this->user()); - $this->call('GET', '/json/tour'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::transactionJournals - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testTransactionJournals($range) - { - $repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); - $paginator = new \Illuminate\Pagination\LengthAwarePaginator(new Collection, 0, 40); - $repository->shouldReceive('getJournals')->withAnyArgs()->once()->andReturn($paginator); - - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/json/transaction-journals/deposit'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\JsonController::trigger - */ - public function testTrigger() - { - $this->be($this->user()); - $this->call('GET', '/json/trigger'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/NewUserControllerTest.php b/tests/acceptance/Controllers/NewUserControllerTest.php deleted file mode 100644 index b39ed42019..0000000000 --- a/tests/acceptance/Controllers/NewUserControllerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -be($this->emptyUser()); - $this->call('GET', '/'); - $this->assertResponseStatus(302); - $this->assertRedirectedToRoute('new-user.index'); - } - - /** - * @covers FireflyIII\Http\Controllers\NewUserController::index - */ - public function testIndexGo() - { - $this->be($this->emptyUser()); - $this->call('GET', '/new-user'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\NewUserController::submit - * @covers FireflyIII\Http\Requests\NewUserFormRequest::authorize - * @covers FireflyIII\Http\Requests\NewUserFormRequest::rules - */ - public function testSubmit() - { - $this->be($this->emptyUser()); - - $args = [ - 'bank_name' => 'New bank', - 'bank_balance' => 100, - 'savings_balance' => 200, - 'credit_card_limit' => 1000, - ]; - - $this->call('POST', '/new-user/submit', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/PiggyBankControllerTest.php b/tests/acceptance/Controllers/PiggyBankControllerTest.php deleted file mode 100644 index b51bda8d6d..0000000000 --- a/tests/acceptance/Controllers/PiggyBankControllerTest.php +++ /dev/null @@ -1,198 +0,0 @@ -be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/piggy-banks/add/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::create - */ - public function testCreate() - { - $this->be($this->user()); - $this->call('GET', '/piggy-banks/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/piggy-banks/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::destroy - */ - public function testDestroy() - { - $this->be($this->user()); - $this->session(['piggy-banks.delete.url' => 'http://localhost']); - $this->call('POST', '/piggy-banks/destroy/2'); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/piggy-banks/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::index - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/piggy-banks'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::order - */ - public function testOrder() - { - $args = [ - 1 => 1, - 2 => 2, - ]; - - $this->be($this->user()); - $this->call('POST', '/piggy-banks/sort', $args); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::postAdd - */ - public function testPostAdd() - { - $args = [ - 'amount' => 100, - ]; - - $this->be($this->user()); - $this->call('POST', '/piggy-banks/add/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::postRemove - */ - public function testPostRemove() - { - $args = [ - 'amount' => 100, - ]; - - $this->be($this->user()); - $this->call('POST', '/piggy-banks/remove/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::remove - */ - public function testRemove() - { - $this->be($this->user()); - $this->call('GET', '/piggy-banks/remove/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::show - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShow($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/piggy-banks/show/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::store - * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::authorize - * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::rules - */ - public function testStore() - { - $this->be($this->user()); - $this->session(['piggy-banks.create.url' => 'http://localhost/']); - - $args = [ - 'name' => 'New', - 'targetamount' => 100, - 'account_id' => 2, - ]; - - $this->call('POST', '/piggy-banks/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\PiggyBankController::update - * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::authorize - * @covers FireflyIII\Http\Requests\PiggyBankFormRequest::rules - */ - public function testUpdate() - { - $this->be($this->user()); - $this->session(['piggy-banks.edit.url' => 'http://localhost/']); - - $args = [ - 'name' => 'Updated', - 'targetamount' => 100, - 'account_id' => 2, - 'id' => 1, - ]; - - $this->call('POST', '/piggy-banks/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/PreferencesControllerTest.php b/tests/acceptance/Controllers/PreferencesControllerTest.php deleted file mode 100644 index a5ecd9b9c0..0000000000 --- a/tests/acceptance/Controllers/PreferencesControllerTest.php +++ /dev/null @@ -1,50 +0,0 @@ -be($this->user()); - $this->call('GET', '/preferences'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\PreferencesController::postIndex - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testPostIndex($range) - { - $args = [ - 'frontPageAccounts' => [1], - 'viewRange' => $range, - 'budgetMaximum' => 100, - 'customFiscalYear' => 1, - 'fiscalYearStart' => '01-01', - 'language' => 'en_US', - ]; - - $this->be($this->user()); - $this->call('POST', '/preferences', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/ProfileControllerTest.php b/tests/acceptance/Controllers/ProfileControllerTest.php deleted file mode 100644 index 442e590ec3..0000000000 --- a/tests/acceptance/Controllers/ProfileControllerTest.php +++ /dev/null @@ -1,83 +0,0 @@ -be($this->user()); - $this->call('GET', '/profile/change-password'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\ProfileController::deleteAccount - */ - public function testDeleteAccount() - { - - $this->be($this->user()); - $this->call('GET', '/profile/delete-account'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\ProfileController::index - */ - public function testIndex() - { - $this->be($this->user()); - $this->call('GET', '/profile'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\ProfileController::postChangePassword - * @covers FireflyIII\Http\Requests\ProfileFormRequest::authorize - * @covers FireflyIII\Http\Requests\ProfileFormRequest::rules - */ - public function testPostChangePassword() - { - $args = [ - 'current_password' => 'james', - 'new_password' => 'sander', - 'new_password_confirmation' => 'sander', - ]; - $this->be($this->user()); - $this->call('POST', '/profile/change-password', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\ProfileController::postDeleteAccount - * @covers FireflyIII\Http\Requests\DeleteAccountFormRequest::authorize - * @covers FireflyIII\Http\Requests\DeleteAccountFormRequest::rules - */ - public function testPostDeleteAccount() - { - $args = [ - 'password' => 'james', - ]; - - $this->be($this->toBeDeletedUser()); - $this->call('POST', '/profile/delete-account', $args); - $this->assertResponseStatus(302); - $this->assertRedirectedToRoute('index'); - $this->assertNull(DB::table('users')->find(3)); - } -} diff --git a/tests/acceptance/Controllers/ReportControllerTest.php b/tests/acceptance/Controllers/ReportControllerTest.php deleted file mode 100644 index 8ce463f191..0000000000 --- a/tests/acceptance/Controllers/ReportControllerTest.php +++ /dev/null @@ -1,130 +0,0 @@ -be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/reports'); - $this->assertResponseStatus(200); - - } - - /** - * @covers FireflyIII\Http\Controllers\ReportController::__construct - * @covers FireflyIII\Http\Controllers\ReportController::report - * @covers FireflyIII\Http\Controllers\ReportController::defaultMonth - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testReportDefaultMonth($range) - { - // mock some stuff. - $accountHelper = $this->mock('FireflyIII\Helpers\Report\AccountReportHelperInterface'); - $budgetHelper = $this->mock('FireflyIII\Helpers\Report\BudgetReportHelperInterface'); - $balanceHelper = $this->mock('FireflyIII\Helpers\Report\BalanceReportHelperInterface'); - $defaultHelper = $this->mock('FireflyIII\Helpers\Report\ReportHelperInterface'); - - - $accountHelper->shouldReceive('getAccountReport')->once()->andReturn(new AccountCollection); - $defaultHelper->shouldReceive('getIncomeReport')->once()->andReturn(new Income); - $defaultHelper->shouldReceive('getExpenseReport')->once()->andReturn(new Expense); - $budgetHelper->shouldReceive('getBudgetReport')->once()->andReturn(new BudgetCollection); - $defaultHelper->shouldReceive('getCategoryReport')->once()->andReturn(new CategoryCollection); - $balanceHelper->shouldReceive('getBalanceReport')->once()->andReturn(new Balance); - $defaultHelper->shouldReceive('getBillReport')->once()->andReturn(new BillCollection); - $defaultHelper->shouldReceive('tagReport')->once()->andReturn([]); - - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/reports/report/default/20160101/20160131/1,2'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\ReportController::report - * @covers FireflyIII\Http\Controllers\ReportController::defaultMultiYear - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testReportDefaultMultiYear($range) - { - $this->be($this->user()); - - // mock some stuff. - $accountHelper = $this->mock('FireflyIII\Helpers\Report\AccountReportHelperInterface'); - $defaultHelper = $this->mock('FireflyIII\Helpers\Report\ReportHelperInterface'); - $budgetRepos = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $categoryRepos = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); - - $budgetRepos->shouldReceive('getActiveBudgets')->once()->andReturn(new Collection); - $categoryRepos->shouldReceive('getCategories')->once()->andReturn(new Collection); - - - $accountHelper->shouldReceive('getAccountReport')->once()->andReturn(new AccountCollection); - $defaultHelper->shouldReceive('getIncomeReport')->once()->andReturn(new Income); - $defaultHelper->shouldReceive('getExpenseReport')->once()->andReturn(new Expense); - $defaultHelper->shouldReceive('tagReport')->once()->andReturn([]); - - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/reports/report/default/20160101/20171231/1,2'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\ReportController::report - * @covers FireflyIII\Http\Controllers\ReportController::defaultYear - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testReportDefaultYear($range) - { - - // mock some stuff. - $accountHelper = $this->mock('FireflyIII\Helpers\Report\AccountReportHelperInterface'); - $defaultHelper = $this->mock('FireflyIII\Helpers\Report\ReportHelperInterface'); - - - $accountHelper->shouldReceive('getAccountReport')->once()->andReturn(new AccountCollection); - $defaultHelper->shouldReceive('getIncomeReport')->once()->andReturn(new Income); - $defaultHelper->shouldReceive('getCategoriesWithExpenses')->andReturn(new Collection); - $defaultHelper->shouldReceive('getExpenseReport')->once()->andReturn(new Expense); - $defaultHelper->shouldReceive('tagReport')->once()->andReturn([]); - - - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/reports/report/default/20160101/20161231/1,2'); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/RuleControllerTest.php b/tests/acceptance/Controllers/RuleControllerTest.php deleted file mode 100644 index 369e0bcc7c..0000000000 --- a/tests/acceptance/Controllers/RuleControllerTest.php +++ /dev/null @@ -1,177 +0,0 @@ -be($this->user()); - $this->call('GET', '/rules/create/1'); - $this->assertResponseStatus(200); - - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/rules/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::destroy - */ - public function testDestroy() - { - $this->session(['rules.rule.delete.url' => 'http://localhost']); - - $this->be($this->user()); - $this->call('POST', '/rules/destroy/1'); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::down - */ - public function testDown() - { - $this->be($this->user()); - $this->call('GET', '/rules/down/1'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::edit - * @covers FireflyIII\Http\Controllers\RuleController::getCurrentTriggers - * @covers FireflyIII\Http\Controllers\RuleController::getCurrentActions - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/rules/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::index - * @covers FireflyIII\Http\Controllers\RuleController::createDefaultRuleGroup - * @covers FireflyIII\Http\Controllers\RuleController::createDefaultRule - */ - public function testIndex() - { - $this->be($this->user()); - $this->call('GET', '/rules'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::reorderRuleActions - */ - public function testReorderRuleActions() - { - $this->be($this->user()); - $args = ['actions' => [1, 2, 3]]; - $this->call('POST', '/rules/action/order/1', $args); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::reorderRuleTriggers - */ - public function testReorderRuleTriggers() - { - $this->be($this->user()); - $args = ['actions' => [1, 2]]; - $this->call('POST', '/rules/trigger/order/1', $args); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::store - * @covers FireflyIII\Http\Requests\RuleFormRequest::authorize - * @covers FireflyIII\Http\Requests\RuleFormRequest::rules - */ - public function testStore() - { - $this->session(['rules.rule.create.url' => 'http://localhost']); - $this->be($this->user()); - $args = [ - 'rule_group_id' => 1, - 'title' => 'Some new rule', - 'user_id' => 1, - 'trigger' => 'store-journal', - 'description' => 'Some new rule', - 'rule-trigger' => ['description_is'], - 'rule-trigger-value' => ['something'], - 'rule-trigger-stop' => [], - 'rule-action' => ['set_category'], - 'rule-action-value' => ['something'], - 'rule-action-stop' => [], - 'stop_processing' => 0, - ]; - $this->call('POST', '/rules/store/1', $args); - - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::up - */ - public function testUp() - { - - $this->be($this->user()); - $this->call('GET', '/rules/up/1'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleController::update - * @covers FireflyIII\Http\Requests\RuleFormRequest::authorize - * @covers FireflyIII\Http\Requests\RuleFormRequest::rules - */ - public function testUpdate() - { - $this->session(['rules.rule.edit.url' => 'http://localhost']); - - $this->be($this->user()); - $args = [ - 'title' => 'Some new rule update', - 'rule_group_id' => 1, - 'id' => 1, - 'active' => 1, - 'trigger' => 'store-journal', - 'description' => 'Some new rule', - 'rule-trigger' => ['description_is'], - 'rule-trigger-value' => ['something'], - 'rule-trigger-stop' => [], - 'rule-action' => ['set_category'], - 'rule-action-value' => ['something'], - 'rule-action-stop' => [], - 'stop_processing' => 0, - ]; - $this->call('POST', '/rules/update/1', $args); - $this->assertSessionHas('success'); - $this->assertResponseStatus(302); - } -} diff --git a/tests/acceptance/Controllers/RuleGroupControllerTest.php b/tests/acceptance/Controllers/RuleGroupControllerTest.php deleted file mode 100644 index eca06576b8..0000000000 --- a/tests/acceptance/Controllers/RuleGroupControllerTest.php +++ /dev/null @@ -1,120 +0,0 @@ -be($this->user()); - $this->call('GET', '/rule-groups/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/rule-groups/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::destroy - */ - public function testDestroy() - { - $this->session(['rules.rule-group.delete.url' => 'http://localhost']); - - $this->be($this->user()); - $this->call('POST', '/rule-groups/destroy/1'); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::down - */ - public function testDown() - { - $this->be($this->user()); - $this->call('GET', '/rule-groups/down/1'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/rule-groups/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::store - * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::authorize - * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::rules - */ - public function testStore() - { - $this->session(['rules.rule-group.create.url' => 'http://localhost']); - $args = [ - 'title' => 'Some new rule group', - 'description' => 'New rules', - ]; - - $this->be($this->user()); - $this->call('POST', '/rule-groups/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::up - */ - public function testUp() - { - $this->be($this->user()); - $this->call('GET', '/rule-groups/up/1'); - $this->assertResponseStatus(302); - } - - /** - * @covers FireflyIII\Http\Controllers\RuleGroupController::update - * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::authorize - * @covers FireflyIII\Http\Requests\RuleGroupFormRequest::rules - */ - public function testUpdate() - { - $this->session(['rules.rule-group.edit.url' => 'http://localhost']); - $args = [ - 'id' => 1, - 'title' => 'Some new rule group X', - 'description' => 'New rules', - 'active' => 1, - ]; - - $this->be($this->user()); - $this->call('POST', '/rule-groups/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/SearchControllerTest.php b/tests/acceptance/Controllers/SearchControllerTest.php deleted file mode 100644 index 3e616bff02..0000000000 --- a/tests/acceptance/Controllers/SearchControllerTest.php +++ /dev/null @@ -1,34 +0,0 @@ -mock('FireflyIII\Support\Search\SearchInterface'); - $searcher->shouldReceive('searchTransactions')->once()->with(['test'])->andReturn(new Collection); - $searcher->shouldReceive('searchAccounts')->once()->with(['test'])->andReturn(new Collection); - $searcher->shouldReceive('searchCategories')->once()->with(['test'])->andReturn(new Collection); - $searcher->shouldReceive('searchBudgets')->once()->with(['test'])->andReturn(new Collection); - $searcher->shouldReceive('searchTags')->once()->with(['test'])->andReturn(new Collection); - - $this->be($this->user()); - $this->call('GET', '/search?q=test&search='); - $this->assertResponseStatus(200); - } -} diff --git a/tests/acceptance/Controllers/TagControllerTest.php b/tests/acceptance/Controllers/TagControllerTest.php deleted file mode 100644 index 817f189451..0000000000 --- a/tests/acceptance/Controllers/TagControllerTest.php +++ /dev/null @@ -1,129 +0,0 @@ -be($this->user()); - $this->call('GET', '/tags/create'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/tags/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::destroy - */ - public function testDestroy() - { - $this->be($this->user()); - $this->call('POST', '/tags/destroy/1'); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/tags/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::hideTagHelp - */ - public function testHideTagHelp() - { - $this->be($this->user()); - $this->call('POST', '/tags/hideTagHelp/true'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::index - */ - public function testIndex() - { - $this->be($this->user()); - $this->call('GET', '/tags'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::show - */ - public function testShow() - { - $this->be($this->user()); - $this->call('GET', '/tags/show/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::store - * @covers FireflyIII\Http\Requests\TagFormRequest::authorize - * @covers FireflyIII\Http\Requests\TagFormRequest::rules - */ - public function testStore() - { - $args = [ - 'tag' => 'Some new tag', - 'tagMode' => 'nothing', - ]; - - $this->session(['tags.create.url' => 'http://localhost']); - - $this->be($this->user()); - $this->call('POST', '/tags/store', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\TagController::update - * @covers FireflyIII\Http\Requests\TagFormRequest::authorize - * @covers FireflyIII\Http\Requests\TagFormRequest::rules - */ - public function testUpdate() - { - $args = [ - 'tag' => 'Some new tag yay', - 'id' => 1, - 'tagMode' => 'nothing', - ]; - - $this->session(['tags.edit.url' => 'http://localhost']); - - $this->be($this->user()); - $this->call('POST', '/tags/update/1', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} diff --git a/tests/acceptance/Controllers/TransactionControllerTest.php b/tests/acceptance/Controllers/TransactionControllerTest.php deleted file mode 100644 index 613104d265..0000000000 --- a/tests/acceptance/Controllers/TransactionControllerTest.php +++ /dev/null @@ -1,157 +0,0 @@ -be($this->user()); - $this->call('GET', '/transactions/create/withdrawal'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::delete - */ - public function testDelete() - { - $this->be($this->user()); - $this->call('GET', '/transaction/delete/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::destroy - */ - public function testDestroy() - { - $this->session(['transactions.delete.url' => 'http://localhost']); - - $this->be($this->user()); - $this->call('POST', '/transaction/destroy/1'); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::edit - */ - public function testEdit() - { - $this->be($this->user()); - $this->call('GET', '/transaction/edit/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::index - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testIndex($range) - { - $journals = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); - $journals->shouldReceive('getJournals')->once()->andReturn(new LengthAwarePaginator([], 0, 50)); - - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/transactions/deposit'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::reorder - */ - public function testReorder() - { - $args = [ - 'ids' => [1], - 'date' => '2015-01-01', - ]; - $this->be($this->user()); - $this->call('POST', '/transaction/reorder', $args); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::show - * @dataProvider dateRangeProvider - * - * @param $range - */ - public function testShow($range) - { - $this->be($this->user()); - $this->changeDateRange($this->user(), $range); - $this->call('GET', '/transaction/show/1'); - $this->assertResponseStatus(200); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::store - * @covers FireflyIII\Http\Requests\JournalFormRequest::authorize - * @covers FireflyIII\Http\Requests\JournalFormRequest::rules - * @covers FireflyIII\Http\Requests\JournalFormRequest::getJournalData - */ - public function testStore() - { - $this->session(['transactions.create.url' => 'http://localhost']); - - $args = [ - 'what' => 'withdrawal', - 'description' => 'Something', - 'source_account_id' => '1', - 'destination_account_name' => 'Some expense', - 'amount' => 100, - 'amount_currency_id_amount' => 1, - 'date' => '2015-01-01', - ]; - $this->be($this->user()); - $this->call('POST', '/transactions/store/withdrawal', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } - - /** - * @covers FireflyIII\Http\Controllers\TransactionController::update - * @covers FireflyIII\Http\Requests\JournalFormRequest::authorize - * @covers FireflyIII\Http\Requests\JournalFormRequest::rules - * @covers FireflyIII\Http\Requests\JournalFormRequest::getJournalData - */ - public function testUpdate() - { - $this->session(['transactions.edit.url' => 'http://localhost']); - - $args = [ - 'what' => 'withdrawal', - 'id' => 4, - 'description' => 'Something new', - 'source_account_id' => '1', - 'destination_account_name' => 'Some expense account', - 'amount' => 100, - 'amount_currency_id_amount' => 1, - 'date' => '2015-01-01', - ]; - $this->be($this->user()); - $this->call('POST', '/transaction/update/4', $args); - $this->assertResponseStatus(302); - $this->assertSessionHas('success'); - } -} From b7b52707fb8800f4140c72e8447fac54eedea1e9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 15:02:07 +0200 Subject: [PATCH 136/206] Fix Travis. --- .travis.yml | 1 - phpunit.xml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100755 phpunit.xml diff --git a/.travis.yml b/.travis.yml index f48b4f9dd9..61763a7648 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ php: - 7 install: - - cp _development/phpunit.xml ./phpunit.xml - phpenv config-rm xdebug.ini - composer selfupdate - rm composer.lock diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 0000000000..31ac89bcfe --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,31 @@ + + + + + ./tests/ + + + + + app/ + + + + + + + + + + + + + From 60d732067bd04dbc80d508438c7f52770dfe3eec Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 15:08:59 +0200 Subject: [PATCH 137/206] Made some things less complex. --- app/Http/Controllers/AccountController.php | 59 +++++++++---------- .../Controllers/TransactionController.php | 38 +++++------- .../Category/CategoryRepository.php | 4 +- 3 files changed, 46 insertions(+), 55 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 5233e5afeb..246ea45efd 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -161,10 +161,8 @@ class AccountController extends Controller $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); $types = config('firefly.accountTypesByIdentifier.' . $what); $accounts = $repository->getAccountsByType($types); - /** @var Carbon $start */ - $start = clone session('start', Carbon::now()->startOfMonth()); - /** @var Carbon $end */ - $end = clone session('end', Carbon::now()->endOfMonth()); + $start = clone session('start', Carbon::now()->startOfMonth()); + $end = clone session('end', Carbon::now()->endOfMonth()); $start->subDay(); $ids = $accounts->pluck('id')->toArray(); @@ -183,32 +181,6 @@ class AccountController extends Controller return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts')); } - /** - * @param ARI $repository - * @param Account $account - * @param string $date - * - * @return View - */ - public function showWithDate(ARI $repository, Account $account, string $date) - { - $carbon = new Carbon($date); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); - $subTitle = $account->name; - $page = intval(Input::get('page')); - $pageSize = Preferences::get('transactionPageSize', 50)->data; - $offset = ($page - 1) * $pageSize; - $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); - $count = $set->count(); - $subSet = $set->splice($offset, $pageSize); - $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); - $journals->setPath('categories/show/' . $account->id . '/' . $date); - - return view('accounts.show_with_date', compact('category', 'journals', 'subTitle', 'carbon')); - } - /** * @param ARI $repository * @param Account $account @@ -251,6 +223,7 @@ class AccountController extends Controller if ($cache->has()) { $entries = $cache->get(); + return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); } @@ -272,6 +245,32 @@ class AccountController extends Controller return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); } + /** + * @param ARI $repository + * @param Account $account + * @param string $date + * + * @return View + */ + public function showWithDate(ARI $repository, Account $account, string $date) + { + $carbon = new Carbon($date); + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($carbon, $range); + $end = Navigation::endOfPeriod($carbon, $range); + $subTitle = $account->name; + $page = intval(Input::get('page')); + $pageSize = Preferences::get('transactionPageSize', 50)->data; + $offset = ($page - 1) * $pageSize; + $set = $repository->journalsInPeriod(new Collection([$account]), [], $start, $end); + $count = $set->count(); + $subSet = $set->splice($offset, $pageSize); + $journals = new LengthAwarePaginator($subSet, $count, $pageSize, $page); + $journals->setPath('categories/show/' . $account->id . '/' . $date); + + return view('accounts.show_with_date', compact('category', 'journals', 'subTitle', 'carbon')); + } + /** * @param AccountFormRequest $request * @param ARI $repository diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 24c1193125..cb2f61e374 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -14,18 +14,15 @@ use Carbon\Carbon; use ExpandedForm; use FireflyIII\Events\TransactionJournalStored; use FireflyIII\Events\TransactionJournalUpdated; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Requests\JournalFormRequest; use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Support\Collection; -use Log; use Preferences; use Response; use Session; @@ -51,31 +48,30 @@ class TransactionController extends Controller } /** - * @param ARI $repository * @param string $what * * @return \Illuminate\View\View */ - public function create(ARI $repository, string $what = TransactionType::DEPOSIT) + public function create(string $what = TransactionType::DEPOSIT) { - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); - $what = strtolower($what); - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account'])); - $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBanks = $piggyRepository->getPiggyBanksWithAmount(); - $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); - $preFilled = Session::has('preFilled') ? session('preFilled') : []; - $subTitle = trans('form.add_new_' . $what); - $subTitleIcon = 'fa-plus'; + $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); + $what = strtolower($what); + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $piggyBanks = $piggyRepository->getPiggyBanksWithAmount(); + $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); + $preFilled = Session::has('preFilled') ? session('preFilled') : []; + $subTitle = trans('form.add_new_' . $what); + $subTitleIcon = 'fa-plus'; Session::put('preFilled', $preFilled); // put previous url in session if not redirect from store (not "create another"). if (session('transactions.create.fromStore') !== true) { $url = URL::previous(); - Log::debug('TransactionController::create. Previous URL is ' . $url); Session::put('transactions.create.url', $url); } Session::forget('transactions.create.fromStore'); @@ -388,7 +384,6 @@ class TransactionController extends Controller * @param JournalRepositoryInterface $repository * * @return View - * @throws FireflyException */ public function show(TransactionJournal $journal, JournalRepositoryInterface $repository) { @@ -410,12 +405,11 @@ class TransactionController extends Controller * @param JournalFormRequest $request * @param JournalRepositoryInterface $repository * - * @param AttachmentHelperInterface $att - * * @return \Illuminate\Http\RedirectResponse */ - public function store(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att) + public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) { + $att = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface'); $doSplit = intval($request->get('split_journal')) === 1; $journalData = $request->getJournalData(); @@ -487,8 +481,6 @@ class TransactionController extends Controller public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal) { $journalData = $request->getJournalData(); - Log::debug('Will update journal ', $journal->toArray()); - Log::debug('Update related data ', $journalData); $repository->update($journal, $journalData); // save attachments: diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index c02a8ad1a1..d4e0ee83e3 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -114,7 +114,7 @@ class CategoryRepository implements CategoryRepositoryInterface $firstJournalQuery->whereIn('t.account_id', $ids); } - $firstJournal = $firstJournalQuery->first(['transaction_journals.*']); + $firstJournal = $firstJournalQuery->first(['transaction_journals.date']); if ($firstJournal) { $first = $firstJournal->date; @@ -131,7 +131,7 @@ class CategoryRepository implements CategoryRepositoryInterface $firstTransactionQuery->whereIn('transactions.account_id', $ids); } - $firstTransaction = $firstTransactionQuery->first(['transaction_journals.*']); + $firstTransaction = $firstTransactionQuery->first(['transaction_journals.date']); if (!is_null($firstTransaction) && ((!is_null($first) && $firstTransaction->date < $first) || is_null($first))) { $first = new Carbon($firstTransaction->date); From 4164ebcc690e22b14a1aa13af6e4d459803129fb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 15:24:23 +0200 Subject: [PATCH 138/206] Added a lot of todo things. --- app/Console/Commands/VerifyDatabase.php | 2 - app/Export/Entry/Entry.php | 13 +- .../Account/ChartJsAccountChartGenerator.php | 3 - .../Events/ConnectJournalToPiggyBank.php | 1 + .../Transaction/SplitController.php | 2 + .../Controllers/TransactionController.php | 37 +-- app/Repositories/Bill/BillRepository.php | 15 +- .../Budget/BudgetRepositoryInterface.php | 223 ------------------ .../Journal/JournalRepository.php | 26 -- .../Journal/JournalRepositoryInterface.php | 12 - app/Repositories/Tag/TagRepository.php | 2 + app/Rules/Triggers/FromAccountContains.php | 1 + app/Rules/Triggers/FromAccountEnds.php | 1 + app/Rules/Triggers/FromAccountIs.php | 1 + app/Rules/Triggers/FromAccountStarts.php | 1 + app/Rules/Triggers/ToAccountContains.php | 1 + app/Rules/Triggers/ToAccountEnds.php | 1 + app/Rules/Triggers/ToAccountIs.php | 1 + app/Rules/Triggers/ToAccountStarts.php | 1 + .../Models/TransactionJournalSupport.php | 45 ++-- 20 files changed, 70 insertions(+), 319 deletions(-) diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index ba53da1aa7..c67ddb2787 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -38,8 +38,6 @@ class VerifyDatabase extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index a7387f13db..d27480b67f 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -10,7 +10,6 @@ declare(strict_types = 1); namespace FireflyIII\Export\Entry; -use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; /** @@ -66,13 +65,11 @@ class Entry $entry->category = new EntryCategory($journal->categories->first()); $entry->bill = new EntryBill($journal->bill); - /** @var Account $sourceAccount */ - $sourceAccount = TransactionJournal::sourceAccount($journal); - $entry->sourceAccount = new EntryAccount($sourceAccount); - - /** @var Account $destination */ - $destination = TransactionJournal::destinationAccount($journal); - $entry->destinationAccount = new EntryAccount($destination); + // TODO support split journals + $sources = TransactionJournal::sourceAccountList($journal); + $entry->sourceAccount = new EntryAccount($sources->first()); + $destinations = TransactionJournal::destinationAccountList($journal); + $entry->destinationAccount = new EntryAccount($destinations->first()); return $entry; diff --git a/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php b/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php index 484a7aa517..04b0851f23 100644 --- a/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php +++ b/app/Generator/Chart/Account/ChartJsAccountChartGenerator.php @@ -83,9 +83,6 @@ class ChartJsAccountChartGenerator implements AccountChartGeneratorInterface */ public function single(Account $account, array $labels, array $dataSet): array { - // language: - $format = (string)trans('config.month_and_day'); - $data = [ 'count' => 1, 'labels' => $labels, diff --git a/app/Handlers/Events/ConnectJournalToPiggyBank.php b/app/Handlers/Events/ConnectJournalToPiggyBank.php index 83089674cc..7ac02550ba 100644 --- a/app/Handlers/Events/ConnectJournalToPiggyBank.php +++ b/app/Handlers/Events/ConnectJournalToPiggyBank.php @@ -44,6 +44,7 @@ class ConnectJournalToPiggyBank $amount = TransactionJournal::amountPositive($journal); // if piggy account matches source account, the amount is positive + // TODO support split journals if ($piggyBank->account_id == TransactionJournal::sourceAccount($journal)->id) { $amount = bcmul($amount, '-1'); } diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 84858d3b61..46bd9577ae 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -30,6 +30,8 @@ use View; * Class SplitController * * @package FireflyIII\Http\Controllers\Transaction + * + * TODO support piggy banks */ class SplitController extends Controller { diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index cb2f61e374..0015bc99a6 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -136,18 +136,20 @@ class TransactionController extends Controller if ($count > 2) { return redirect(route('split.journal.edit', [$journal->id])); } - $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); - $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks()); - $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); - $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); - $uploadSize = min($maxFileSize, $maxPostSize); - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); - $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - $preFilled = [ + $accountRepository = app('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); + $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); + $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks()); + $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); + $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); + $uploadSize = min($maxFileSize, $maxPostSize); + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); + $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $preFilled = [ 'date' => TransactionJournal::dateAsString($journal), 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), @@ -156,16 +158,19 @@ class TransactionController extends Controller 'budget_id' => TransactionJournal::budgetId($journal), 'piggy_bank_id' => TransactionJournal::piggyBankId($journal), 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), - 'source_account_id' => TransactionJournal::sourceAccount($journal)->id, - 'source_account_name' => TransactionJournal::sourceAccount($journal)->name, - 'destination_account_id' => TransactionJournal::destinationAccount($journal)->id, - 'destination_account_name' => TransactionJournal::destinationAccount($journal)->name, + 'source_account_id' => $sourceAccounts->first()->id, + 'source_account_name' => $sourceAccounts->first()->name, + 'destination_account_id' => $destinationAccounts->first()->id, + 'destination_account_name' => $destinationAccounts->first()->name, 'amount' => TransactionJournal::amountPositive($journal), ]; + // TODO support split withdrawal if ($journal->isWithdrawal() && TransactionJournal::destinationAccountTypeStr($journal) == 'Cash account') { $preFilled['destination_account_name'] = ''; } + + // TODO support split withdrawal if ($journal->isDeposit() && TransactionJournal::sourceAccountTypeStr($journal) == 'Cash account') { $preFilled['source_account_name'] = ''; } diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 60fb1ab575..8964cde01d 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -5,13 +5,10 @@ namespace FireflyIII\Repositories\Bill; use Carbon\Carbon; use DB; -use FireflyIII\Models\Account; use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\User; -use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -431,12 +428,12 @@ class BillRepository implements BillRepositoryInterface if (false === $journal->isWithdrawal()) { return false; } - - $matches = explode(',', $bill->match); - $description = strtolower($journal->description) . ' ' . strtolower(TransactionJournal::destinationAccount($journal)->name); - - // new: add source to word match: - $description .= ' ' . strtolower(TransactionJournal::sourceAccount($journal)->name); + $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $matches = explode(',', $bill->match); + $description = strtolower($journal->description) . ' '; + $description .= strtolower(join(' ', $destinationAccounts->pluck('name')->toArray())); + $description .= strtolower(join(' ', $sourceAccounts->pluck('name')->toArray())); $wordMatch = $this->doWordMatch($matches, $description); $amountMatch = $this->doAmountMatch(TransactionJournal::amountPositive($journal), $bill->amount_min, $bill->amount_max); diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 5c34a58bd7..10e9adf536 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -23,24 +23,6 @@ interface BudgetRepositoryInterface */ public function destroy(Budget $budget): bool; - // /** - // * - // * Same as ::spentInPeriod but corrects journals for a set of accounts - // * - // * @param Budget $budget - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return string - // */ - // public function balanceInPeriod(Budget $budget, Carbon $start, Carbon $end, Collection $accounts); - - // /** - // * @return bool - // */ - // public function cleanupBudgets(): bool; - /** * Find a budget. * @@ -60,28 +42,11 @@ interface BudgetRepositoryInterface */ public function firstUseDate(Budget $budget): Carbon; - // /** - // * @param Budget $budget - // * @param Account $account - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function expensesSplit(Budget $budget, Account $account, Carbon $start, Carbon $end): Collection; - /** * @return Collection */ public function getActiveBudgets(): Collection; - // /** - // * @param Budget $budget - // * - // * @return Carbon - // */ - // public function firstActivity(Budget $budget): Carbon; - /** * @param Carbon $start * @param Carbon $end @@ -95,111 +60,11 @@ interface BudgetRepositoryInterface */ public function getBudgets(): Collection; - // /** - // * @param Account $account - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getAllWithoutBudget(Account $account, Collection $accounts, Carbon $start, Carbon $end): Collection; - - // /** - // * Get the budgeted amounts for each budgets in each year. - // * - // * @param Collection $budgets - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getBudgetedPerYear(Collection $budgets, Carbon $start, Carbon $end): Collection; - /** * @return Collection */ public function getInactiveBudgets(): Collection; - // /** - // * Returns an array with every budget in it and the expenses for each budget - // * per month. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return array - // */ - // public function getBudgetsAndExpensesPerMonth(Collection $accounts, Carbon $start, Carbon $end): array; - - // /** - // * Returns an array with every budget in it and the expenses for each budget - // * per year for. - // * - // * @param Collection $budgets - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @deprecated - // * - // * @return array - // */ - // public function getBudgetsAndExpensesPerYear(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array; - - // /** - // * Returns a list of budgets, budget limits and limit repetitions - // * (doubling any of them in a left join) - // * - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end): Collection; - - // /** - // * Returns a list of budget limits that are valid in the current given range. - // * - // * @param Budget $budget - // * @param Carbon $start - // * @param Carbon $end - // * @param LimitRepetition $ignore - // * - // * @return Collection - // */ - // public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection; - - // /** - // * @param Budget $budget - // * @param string $repeatFreq - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return LimitRepetition - // */ - // public function getCurrentRepetition(Budget $budget, string $repeatFreq, Carbon $start, Carbon $end): LimitRepetition; - - // /** - // * Returns all expenses for the given budget and the given accounts, in the given period. - // * - // * @param Budget $budget - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getExpenses(Budget $budget, Collection $accounts, Carbon $start, Carbon $end):Collection; - - // /** - // * @param Budget $budget - // * - // * @return Carbon - // */ - // public function getFirstBudgetLimitDate(Budget $budget):Carbon; - /** * @param Collection $budgets * @param Collection $accounts @@ -238,94 +103,6 @@ interface BudgetRepositoryInterface */ public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string; - // /** - // * Returns all the transaction journals for a limit, possibly limited by a limit repetition. - // * - // * @param Budget $budget - // * @param LimitRepetition $repetition - // * @param int $take - // * - // * @return LengthAwarePaginator - // */ - // public function getJournals(Budget $budget, LimitRepetition $repetition = null, int $take = 50): LengthAwarePaginator; - - // /** - // * @param Carbon $start - // * @param Carbon $end - // * @param int $page - // * @param int $pageSize - // * - // * @return LengthAwarePaginator - // */ - // public function getWithoutBudget(Carbon $start, Carbon $end, int $page, int $pageSize = 50): LengthAwarePaginator; - - // /** - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function getWithoutBudgetForAccounts(Collection $accounts, Carbon $start, Carbon $end): Collection; - - // /** - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return string - // */ - // public function getWithoutBudgetSum(Collection $accounts, Carbon $start, Carbon $end): string; - - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * That array contains: - // * - // * budgetid: - // * - // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget - // * from the given users accounts.. - // * - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return array - // */ - // public function spentAllPerDayForAccounts(Collection $accounts, Carbon $start, Carbon $end): array; - - // /** - // * Returns a list of expenses (in the field "spent", grouped per budget per account. - // * - // * @param Collection $budgets - // * @param Collection $accounts - // * @param Carbon $start - // * @param Carbon $end - // * - // * @return Collection - // */ - // public function spentPerBudgetPerAccount(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): Collection; - - // /** - // * Returns an array with the following key:value pairs: - // * - // * yyyy-mm-dd: - // * - // * Where yyyy-mm-dd is the date and is the money spent using WITHDRAWALS in the $budget - // * from all the users accounts. - // * - // * @param Budget $budget - // * @param Carbon $start - // * @param Carbon $end - // * @param Collection $accounts - // * - // * @return array - // */ - // public function spentPerDay(Budget $budget, Carbon $start, Carbon $end, Collection $accounts): array; - /** * @param array $data * diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 3ec470e6a3..1d7099779d 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -131,32 +131,6 @@ class JournalRepository implements JournalRepositoryInterface return $entry; } - /** - * @deprecated - * - * @param TransactionJournal $journal - * @param Transaction $transaction - * - * @return string - */ - public function getAmountBefore(TransactionJournal $journal, Transaction $transaction): string - { - $set = $transaction->account->transactions()->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' - ) - ->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d')) - ->where('transaction_journals.order', '>=', $journal->order) - ->where('transaction_journals.id', '!=', $journal->id) - ->get(['transactions.*']); - $sum = '0'; - foreach ($set as $entry) { - $sum = bcadd($entry->amount, $sum); - } - - return $sum; - - } - /** * @param array $types * @param int $page diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index f93e8f9c74..b55a74f32b 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -50,18 +50,6 @@ interface JournalRepositoryInterface */ public function first(): TransactionJournal; - /** - * Returns the amount in the account before the specified transaction took place. - * - * @deprecated - * - * @param TransactionJournal $journal - * @param Transaction $transaction - * - * @return string - */ - public function getAmountBefore(TransactionJournal $journal, Transaction $transaction): string; - /** * Returns a page of a specific type(s) of journal. * diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index c376c755e6..864df83367 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -229,9 +229,11 @@ class TagRepository implements TagRepositoryInterface // $checkAccount is the source_account for a withdrawal // $checkAccount is the destination_account for a deposit + // TODO match split journals if ($check->isWithdrawal() && TransactionJournal::sourceAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) { $match = false; } + // TODO match split journals if ($check->isDeposit() && TransactionJournal::destinationAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) { $match = false; } diff --git a/app/Rules/Triggers/FromAccountContains.php b/app/Rules/Triggers/FromAccountContains.php index 58799b60d4..fe2abb7e4f 100644 --- a/app/Rules/Triggers/FromAccountContains.php +++ b/app/Rules/Triggers/FromAccountContains.php @@ -52,6 +52,7 @@ final class FromAccountContains extends AbstractTrigger implements TriggerInterf */ public function triggered(TransactionJournal $journal): bool { + // TODO support split withdrawals $fromAccountName = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $search = strtolower($this->triggerValue); $strpos = strpos($fromAccountName, $search); diff --git a/app/Rules/Triggers/FromAccountEnds.php b/app/Rules/Triggers/FromAccountEnds.php index 96e83fadcf..a588742988 100644 --- a/app/Rules/Triggers/FromAccountEnds.php +++ b/app/Rules/Triggers/FromAccountEnds.php @@ -52,6 +52,7 @@ final class FromAccountEnds extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { + // TODO support split withdrawals $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $nameLength = strlen($name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/FromAccountIs.php b/app/Rules/Triggers/FromAccountIs.php index 061044cf7d..fb4aae7006 100644 --- a/app/Rules/Triggers/FromAccountIs.php +++ b/app/Rules/Triggers/FromAccountIs.php @@ -52,6 +52,7 @@ final class FromAccountIs extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { + // TODO support split withdrawals $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/FromAccountStarts.php b/app/Rules/Triggers/FromAccountStarts.php index 62eaafac67..f53a64ba28 100644 --- a/app/Rules/Triggers/FromAccountStarts.php +++ b/app/Rules/Triggers/FromAccountStarts.php @@ -52,6 +52,7 @@ final class FromAccountStarts extends AbstractTrigger implements TriggerInterfac */ public function triggered(TransactionJournal $journal): bool { + // TODO support split withdrawals $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/ToAccountContains.php b/app/Rules/Triggers/ToAccountContains.php index f6d7c31c68..12e138dcdd 100644 --- a/app/Rules/Triggers/ToAccountContains.php +++ b/app/Rules/Triggers/ToAccountContains.php @@ -52,6 +52,7 @@ final class ToAccountContains extends AbstractTrigger implements TriggerInterfac */ public function triggered(TransactionJournal $journal): bool { + // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $search = strtolower($this->triggerValue); $strpos = strpos($toAccountName, $search); diff --git a/app/Rules/Triggers/ToAccountEnds.php b/app/Rules/Triggers/ToAccountEnds.php index f01d8d8ba5..220730ac50 100644 --- a/app/Rules/Triggers/ToAccountEnds.php +++ b/app/Rules/Triggers/ToAccountEnds.php @@ -52,6 +52,7 @@ final class ToAccountEnds extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { + // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $toAccountNameLength = strlen($toAccountName); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/ToAccountIs.php b/app/Rules/Triggers/ToAccountIs.php index 6d1ea0181e..8a2f46ea14 100644 --- a/app/Rules/Triggers/ToAccountIs.php +++ b/app/Rules/Triggers/ToAccountIs.php @@ -52,6 +52,7 @@ final class ToAccountIs extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { + // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/ToAccountStarts.php b/app/Rules/Triggers/ToAccountStarts.php index 8f00f80d0c..64bebd5ce1 100644 --- a/app/Rules/Triggers/ToAccountStarts.php +++ b/app/Rules/Triggers/ToAccountStarts.php @@ -52,6 +52,7 @@ final class ToAccountStarts extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { + // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Support/Models/TransactionJournalSupport.php b/app/Support/Models/TransactionJournalSupport.php index b83bec79b5..86ac894a81 100644 --- a/app/Support/Models/TransactionJournalSupport.php +++ b/app/Support/Models/TransactionJournalSupport.php @@ -184,6 +184,8 @@ class TransactionJournalSupport extends Model } /** + * @deprecated + * * @param TransactionJournal $journal * * @return string @@ -198,6 +200,7 @@ class TransactionJournalSupport extends Model return $cache->get(); } + $account = self::destinationAccount($journal); $type = $account->accountType ? $account->accountType->type : '(unknown)'; $cache->store($type); @@ -205,6 +208,26 @@ class TransactionJournalSupport extends Model return $type; } + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public static function destinationTransactionList(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('transaction-journal'); + $cache->addProperty('destination-transaction-list'); + if ($cache->has()) { + return $cache->get(); + } + $list = $journal->transactions()->where('amount', '>', 0)->with('account')->get(); + $cache->store($list); + + return $list; + } + /** * @param Builder $query * @param string $table @@ -305,6 +328,8 @@ class TransactionJournalSupport extends Model } /** + * @deprecated + * * @param TransactionJournal $journal * * @return string @@ -346,26 +371,6 @@ class TransactionJournalSupport extends Model return $list; } - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public static function destinationTransactionList(TransactionJournal $journal): Collection - { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('destination-transaction-list'); - if ($cache->has()) { - return $cache->get(); - } - $list = $journal->transactions()->where('amount', '>', 0)->with('account')->get(); - $cache->store($list); - - return $list; - } - /** * @param TransactionJournal $journal * From bd818b2dea9ef48c86b8ef975f8915a7350d90d3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 15:39:22 +0200 Subject: [PATCH 139/206] Code clean up. --- app/Helpers/Report/BalanceReportHelper.php | 2 - app/Helpers/Report/BudgetReportHelper.php | 1 - app/Http/Controllers/BudgetController.php | 2 +- .../Controllers/Chart/BudgetController.php | 7 +- app/Http/Controllers/HomeController.php | 4 - app/Http/Controllers/TagController.php | 6 +- .../Account/AccountRepository.php | 3 - app/Repositories/Budget/BudgetRepository.php | 2 - .../Category/CategoryRepository.php | 7 - .../Journal/JournalRepository.php | 22 +- app/Rules/TransactionMatcher.php | 2 - app/Support/ExpandedForm.php | 1 - database/seeds/SplitDataSeeder.php | 282 ------------------ 13 files changed, 8 insertions(+), 333 deletions(-) delete mode 100644 database/seeds/SplitDataSeeder.php diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index 355db28dc0..fe2cc38ea5 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -70,9 +70,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface Log::debug('Build new report.'); // build a balance header: $header = new BalanceHeader; - $budgets = $this->budgetRepository->getBudgets(); $limitRepetitions = $this->budgetRepository->getAllBudgetLimitRepetitions($start, $end); - $spentData = $this->budgetRepository->journalsInPeriod($budgets, $accounts, $start, $end); foreach ($accounts as $account) { $header->addAccount($account); Log::debug('Add account #' . $account->id . ' to header.'); diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 68449f2b39..e529d82d30 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -54,7 +54,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface // spent for budget in time range: $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end); - // $spent = array_sum($totalSpent); if ($spent > 0) { $budgetLine = new BudgetLine; $budgetLine->setBudget($budget); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 3c1873010c..2c19d9a440 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -180,8 +180,8 @@ class BudgetController extends Controller /** * Do some cleanup: + * TODO reimplement the deletion of budget_limits and limit_repetitions with amount 0 */ - // $repository->cleanupBudgets(); // loop the budgets: /** @var Budget $budget */ diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 094861e164..da4a29fd9a 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -9,7 +9,6 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; @@ -110,7 +109,7 @@ class BudgetController extends Controller $cache->addProperty($repetition->id); if ($cache->has()) { - return Response::json($cache->get()); + return Response::json($cache->get()); } $entries = new Collection; @@ -135,11 +134,9 @@ class BudgetController extends Controller * * @param BudgetRepositoryInterface $repository * - * @param ARI $accountRepository - * * @return \Symfony\Component\HttpFoundation\Response */ - public function frontpage(BudgetRepositoryInterface $repository, ARI $accountRepository) + public function frontpage(BudgetRepositoryInterface $repository) { $start = session('start', Carbon::now()->startOfMonth()); $end = session('end', Carbon::now()->endOfMonth()); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 51577790b9..d0fd412f85 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -151,10 +151,6 @@ class HomeController extends Controller { // these routes are not relevant for the help pages: $ignore = [ - // 'logout', 'register', 'bills.rescan', 'attachments.download', 'attachments.preview', - // 'budgets.income', 'csv.download-config', 'currency.default', 'export.status', 'export.download', - // 'json.', 'help.', 'piggy-banks.addMoney', 'piggy-banks.removeMoney', 'rules.rule.up', 'rules.rule.down', - // 'rules.rule-group.up', 'rules.rule-group.down', 'debugbar', ]; $routes = Route::getRoutes(); /** @var \Illuminate\Routing\Route $route */ diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index db5e6dbae5..4add6f4e5e 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -113,13 +113,11 @@ class TagController extends Controller } /** - * @param Tag $tag - * - * @param TagRepositoryInterface $repository + * @param Tag $tag * * @return \Illuminate\View\View */ - public function edit(Tag $tag, TagRepositoryInterface $repository) + public function edit(Tag $tag) { $subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]); $subTitleIcon = 'fa-tag'; diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 40e6f71bf1..dbb25391d7 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -82,7 +82,6 @@ class AccountRepository implements AccountRepositoryInterface */ public function earnedInPeriod(Collection $accounts, Carbon $start, Carbon $end): string { - Log::debug('earnedinperiod'); $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; $sum = bcmul($this->sumInPeriod($accounts, $types, $start, $end), '-1'); @@ -438,7 +437,6 @@ class AccountRepository implements AccountRepositoryInterface */ public function spentInPeriod(Collection $accounts, Carbon $start, Carbon $end): string { - Log::debug('spentinperiod'); $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; $sum = $this->sumInPeriod($accounts, $types, $start, $end); @@ -757,7 +755,6 @@ class AccountRepository implements AccountRepositoryInterface $second = strval($query->sum('t.amount')); $sum = bcadd($first, $second); - Log::debug('SumInPeriodData ', ['accounts' => $accountIds, 'first' => $first, 'second' => $second, 'sum' => $sum]); return $sum; } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 1d7997e4ba..f92cf4c9f3 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -205,8 +205,6 @@ class BudgetRepository implements BudgetRepositoryInterface // get them: $journals = $journalQuery->get(TransactionJournal::queryFields()); - //Log::debug('journalsInPeriod journal count is ' . $journals->count()); - // then get transactions themselves. $transactionQuery = $this->user->transactionjournals() ->expanded() diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index d4e0ee83e3..d9a87005e3 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -198,9 +198,6 @@ class CategoryRepository implements CategoryRepositoryInterface ); // create paginator $offset = ($page - 1) * $pageSize; - Log::debug('Page is ' . $page); - Log::debug('Offset is ' . $offset); - Log::debug('pagesize is ' . $pageSize); $subSet = $complete->slice($offset, $pageSize)->all(); $paginator = new LengthAwarePaginator($subSet, $complete->count(), $pageSize, $page); @@ -343,7 +340,6 @@ class CategoryRepository implements CategoryRepositoryInterface /** @var TransactionJournal $first */ $lastJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC'); - Log::debug('lastUseDate ' . $category->name . ' (' . $category->id . ')'); if ($accounts->count() > 0) { // filter journals: @@ -356,7 +352,6 @@ class CategoryRepository implements CategoryRepositoryInterface if ($lastJournal) { $last = $lastJournal->date; - Log::debug('last is now ' . $last); } // check transactions: @@ -371,8 +366,6 @@ class CategoryRepository implements CategoryRepositoryInterface } $lastTransaction = $lastTransactionQuery->first(['transaction_journals.*']); - if (!is_null($lastTransaction)) { - } if (!is_null($lastTransaction) && ((!is_null($last) && $lastTransaction->date < $last) || is_null($last))) { $last = new Carbon($lastTransaction->date); } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 1d7099779d..fca7a5dbac 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -122,11 +122,9 @@ class JournalRepository implements JournalRepositoryInterface $entry = $this->user->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); if (is_null($entry)) { - Log::debug('Could not find first transaction journal.'); return new TransactionJournal; } - Log::debug('Found first journal: ', ['date' => $entry->date->format('Y-m-d')]); return $entry; } @@ -145,8 +143,7 @@ class JournalRepository implements JournalRepositoryInterface if (count($types) > 0) { $query->transactionTypes($types); } - $count = $this->user->transactionJournals()->transactionTypes($types)->count(); - Log::debug('getJournals() count: ' . $count); + $count = $this->user->transactionJournals()->transactionTypes($types)->count(); $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); @@ -205,7 +202,6 @@ class JournalRepository implements JournalRepositoryInterface * @param TransactionJournal $journal * * @return Collection - * @throws FireflyException */ public function getTransactions(TransactionJournal $journal): Collection { @@ -231,8 +227,8 @@ class JournalRepository implements JournalRepositoryInterface /** @var Collection $transactions */ $transactions = $journal->transactions() - ->groupBy('transactions.id') - ->orderBy('transactions.id')->get( + ->groupBy('transactions.id') + ->orderBy('transactions.id')->get( ['transactions.*', DB::raw('SUM(`transactions`.`amount`) as `sum`')] ); break; @@ -253,10 +249,6 @@ class JournalRepository implements JournalRepositoryInterface ); $transactions->push($final); break; - default: - - throw new FireflyException('Cannot handle ' . $journal->transactionType->type); - break; } // foreach do balance thing $transactions->each( @@ -469,27 +461,22 @@ class JournalRepository implements JournalRepositoryInterface { $sourceAccount = null; $destinationAccount = null; - Log::debug('Now in storeAccounts()'); switch ($type->type) { case TransactionType::WITHDRAWAL: - Log::debug('Now in storeAccounts()::withdrawal'); list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data); break; case TransactionType::DEPOSIT: - Log::debug('Now in storeAccounts()::deposit'); list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data); break; case TransactionType::TRANSFER: - Log::debug('Now in storeAccounts()::transfer'); $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(); $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(); break; default: throw new FireflyException('Did not recognise transaction type.'); } - Log::debug('Now in storeAccounts(), continued.'); if (is_null($destinationAccount)) { Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]); @@ -540,10 +527,8 @@ class JournalRepository implements JournalRepositoryInterface private function storeWithdrawalAccounts(array $data): array { $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); - Log::debug('Now in storeWithdrawalAccounts() with ', ['name' => $data['destination_account_name'], 'len' => strlen($data['destination_account_name'])]); if (strlen($data['destination_account_name']) > 0) { - Log::debug('Now in storeWithdrawalAccounts()'); $destinationType = AccountType::where('type', 'Expense account')->first(); $destinationAccount = Account::firstOrCreateEncrypted( [ @@ -553,7 +538,6 @@ class JournalRepository implements JournalRepositoryInterface 'active' => 1, ] ); - Log::debug('Errors: ', ['err' => $destinationAccount->getErrors()->toArray(), 'id' => $destinationAccount->id]); return [$sourceAccount, $destinationAccount]; } diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php index d14911e212..8bcda69f4d 100644 --- a/app/Rules/TransactionMatcher.php +++ b/app/Rules/TransactionMatcher.php @@ -72,8 +72,6 @@ class TransactionMatcher // - the maximum number of transactions to search in have been searched do { // Fetch a batch of transactions from the database - //$offset = $page > 0 ? ($page - 1) * $pagesize : 0; - //$set = $this->repository->getCollectionOfTypes($this->transactionTypes, $offset, $pagesize); $paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize); $set = $paginator->getCollection(); diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 8f503e7117..68b19c30fb 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -343,7 +343,6 @@ class ExpandedForm $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); $classes = $this->getHolderClasses($name); - //$value = $this->fillFieldValue($name, $value); $html = view('form.static', compact('classes', 'name', 'label', 'value', 'options'))->render(); return $html; diff --git a/database/seeds/SplitDataSeeder.php b/database/seeds/SplitDataSeeder.php deleted file mode 100644 index 8e734eb4ae..0000000000 --- a/database/seeds/SplitDataSeeder.php +++ /dev/null @@ -1,282 +0,0 @@ - 'Checking Account', 'iban' => 'NL11XOLA6707795988', 'meta' => ['accountRole' => 'defaultAsset']], - ['name' => 'Alternate Checking Account', 'iban' => 'NL40UKBK3619908726', 'meta' => ['accountRole' => 'defaultAsset']], - ['name' => 'Savings Account', 'iban' => 'NL96DZCO4665940223', 'meta' => ['accountRole' => 'savingAsset']], - ['name' => 'Shared Checking Account', 'iban' => 'NL81RCQZ7160379858', 'meta' => ['accountRole' => 'sharedAsset']]]; - - // some asset accounts - TestData::createAssetAccounts($user, $assets); - - // budgets, categories and others: - TestData::createBudgets($user); - TestData::createCategories($user); - TestData::createExpenseAccounts($user); - TestData::createRevenueAccounts($user); - TestData::createPiggybanks($user, 'Savings Account'); - TestData::createBills($user); - // some bills - - /* - * Create splitted expense of 66,- - */ - $today = new Carbon('2012-03-14'); - - if (!$skipWithdrawal) { - $this->generateWithdrawals($user); - } - - // create splitted income of 99,- - $today->addDay(); - - if (!$skipDeposit) { - $this->generateDeposits($user); - } - // create a splitted transfer of 57,- (19) - // $today->addDay(); - - if (!$skipTransfer) { - $this->generateTransfers(); - } - } - - /** - * @param User $user - */ - private function generateDeposits(User $user) - { - /* - * DEPOSIT ONE - */ - $sources = ['Work SixtyFive', 'Work EightyFour']; - $categories = ['Salary', 'Reimbursements']; - $amounts = [50, 50]; - $destination = TestData::findAccount($user, 'Alternate Checking Account'); - $date = new Carbon('2012-03-12'); - $journal = TransactionJournal::create( - ['user_id' => $user->id, 'transaction_type_id' => 2, 'transaction_currency_id' => 1, 'description' => 'Split Even Income (journal (50/50))', - 'completed' => 1, 'date' => $date->format('Y-m-d'),] - ); - - foreach ($sources as $index => $source) { - $cat = $categories[$index]; - $source = TestData::findAccount($user, $source); - $one = Transaction::create( - ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, - 'description' => 'Split Even Income #' . $index,] - ); - $two = Transaction::create( - ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], - 'description' => 'Split Even Income #' . $index,] - ); - - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - - /* - * DEPOSIT TWO. - */ - - - $sources = ['Work SixtyFive', 'Work EightyFour', 'Work Fiftyone']; - $categories = ['Salary', 'Bills', 'Reimbursements']; - $amounts = [15, 34, 51]; - $destination = TestData::findAccount($user, 'Checking Account'); - $date = new Carbon; - $date->subDays(3); - $journal = TransactionJournal::create( - ['user_id' => $user->id, 'transaction_type_id' => 2, 'transaction_currency_id' => 1, - 'description' => 'Split Uneven Income (journal (15/34/51=100))', 'completed' => 1, 'date' => $date->format('Y-m-d'),] - ); - - foreach ($sources as $index => $source) { - $cat = $categories[$index]; - $source = TestData::findAccount($user, $source); - $one = Transaction::create( - ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, - 'description' => 'Split Uneven Income #' . $index,] - ); - $two = Transaction::create( - ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], - 'description' => 'Split Uneven Income #' . $index,] - ); - - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - } - - private function generateTransfers() - { - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, - 'transaction_type_id' => 3, // transfer - 'transaction_currency_id' => 1, - 'description' => 'Split Transfer (journal)', - 'completed' => 1, - 'date' => $today->format('Y-m-d'), - ] - ); - - - $source = TestData::findAccount($user, 'Alternate Checking Account'); - $destinations = ['Checking Account', 'Savings Account', 'Shared Checking Account']; - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - - $one = Transaction::create( - [ - 'account_id' => $source->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '-19', - - ] - ); - - $two = Transaction::create( - [ - 'account_id' => $destination->id, - 'transaction_journal_id' => $journal->id, - 'amount' => '19', - - ] - ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); - - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - } - - /** - * @param User $user - */ - private function generateWithdrawals(User $user) - { - /* - * TRANSACTION ONE - */ - $destinations = ['SixtyFive', 'EightyFour']; - $budgets = ['Groceries', 'Car']; - $categories = ['Bills', 'Bills']; - $amounts = [50, 50]; - $source = TestData::findAccount($user, 'Alternate Checking Account'); - $date = new Carbon('2012-03-15'); - $journal = TransactionJournal::create( - ['user_id' => $user->id, 'transaction_type_id' => 1, 'transaction_currency_id' => 1, 'description' => 'Split Even Expense (journal (50/50))', - 'completed' => 1, 'date' => $date->format('Y-m-d'),] - ); - - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - $one = Transaction::create( - ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, - 'description' => 'Split Even Expense #' . $index,] - ); - $two = Transaction::create( - ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], - 'description' => 'Split Even Expense #' . $index,] - ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - - /* - * TRANSACTION TWO. - */ - - - $destinations = ['SixtyFive', 'EightyFour', 'Fiftyone']; - $budgets = ['Groceries', 'Groceries', 'Car']; - $categories = ['Bills', 'Bills', 'Car']; - $amounts = [15, 34, 51]; - $source = TestData::findAccount($user, 'Checking Account'); - $date = new Carbon; - $journal = TransactionJournal::create( - ['user_id' => $user->id, 'transaction_type_id' => 1, 'transaction_currency_id' => 1, - 'description' => 'Split Uneven Expense (journal (15/34/51=100))', 'completed' => 1, 'date' => $date->format('Y-m-d'),] - ); - - foreach ($destinations as $index => $dest) { - $bud = $budgets[$index]; - $cat = $categories[$index]; - $destination = TestData::findAccount($user, $dest); - $one = Transaction::create( - ['account_id' => $source->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index] * -1, - 'description' => 'Split Uneven Expense #' . $index,] - ); - $two = Transaction::create( - ['account_id' => $destination->id, 'transaction_journal_id' => $journal->id, 'amount' => $amounts[$index], - 'description' => 'Split Uneven Expense #' . $index,] - ); - - $one->budgets()->save(TestData::findBudget($user, $bud)); - $two->budgets()->save(TestData::findBudget($user, $bud)); - $one->categories()->save(TestData::findCategory($user, $cat)); - $two->categories()->save(TestData::findCategory($user, $cat)); - } - } -} From 6a750a998fd5394e55e77925d00dd54dced5e0f7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 15 May 2016 16:13:05 +0200 Subject: [PATCH 140/206] Remove category chart from report controller. --- app/Crud/Split/Journal.php | 13 ++++ app/Crud/Split/JournalInterface.php | 6 ++ app/Export/Entry/Entry.php | 1 - .../Events/ConnectJournalToPiggyBank.php | 1 - app/Helpers/Csv/Data.php | 6 +- app/Helpers/Report/ReportHelper.php | 32 ---------- app/Helpers/Report/ReportHelperInterface.php | 9 --- .../Controllers/Chart/CategoryController.php | 53 ---------------- app/Http/Controllers/ReportController.php | 5 +- .../Transaction/SplitController.php | 5 +- .../Controllers/TransactionController.php | 7 +-- app/Http/routes.php | 1 - app/Models/Account.php | 2 +- .../ExportJob/ExportJobRepository.php | 3 - app/Repositories/Rule/RuleRepository.php | 2 +- .../Shared/ComponentRepository.php | 63 ------------------- app/Repositories/Tag/TagRepository.php | 2 - app/Rules/Triggers/FromAccountContains.php | 1 - app/Rules/Triggers/FromAccountEnds.php | 1 - app/Rules/Triggers/FromAccountIs.php | 1 - app/Rules/Triggers/FromAccountStarts.php | 1 - app/Rules/Triggers/ToAccountContains.php | 1 - app/Rules/Triggers/ToAccountEnds.php | 1 - app/Rules/Triggers/ToAccountIs.php | 1 - app/Rules/Triggers/ToAccountStarts.php | 1 - public/js/ff/boxes.js | 61 ------------------ public/js/ff/reports/default/year.js | 9 --- resources/views/accounts/index.twig | 1 - resources/views/accounts/show.twig | 1 - resources/views/accounts/show_with_date.twig | 1 - resources/views/bills/index.twig | 1 - resources/views/bills/show.twig | 1 - resources/views/budgets/index.twig | 21 ------- resources/views/categories/index.twig | 1 - resources/views/csv/column-roles.twig | 12 ---- resources/views/csv/download-config.twig | 6 -- resources/views/csv/index.twig | 12 ---- resources/views/csv/map.twig | 12 ---- resources/views/csv/process.twig | 6 -- resources/views/index.twig | 26 +------- resources/views/layout/default.twig | 1 - resources/views/piggy-banks/show.twig | 1 - resources/views/reports/default/year.twig | 26 -------- resources/views/rules/index.twig | 5 -- resources/views/split/journals/create.twig | 15 ----- resources/views/split/journals/edit.twig | 15 ----- resources/views/tags/index.twig | 8 --- resources/views/tags/show.twig | 3 - 48 files changed, 30 insertions(+), 434 deletions(-) delete mode 100644 app/Repositories/Shared/ComponentRepository.php delete mode 100644 public/js/ff/boxes.js diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php index 192a829778..0a0664ac33 100644 --- a/app/Crud/Split/Journal.php +++ b/app/Crud/Split/Journal.php @@ -40,6 +40,19 @@ class Journal implements JournalInterface $this->user = $user; } + /** + * @param $journal + * + * @return bool + */ + public function markAsComplete(TransactionJournal $journal) + { + $journal->completed = 1; + $journal->save(); + + return true; + } + /** * @param array $data * diff --git a/app/Crud/Split/JournalInterface.php b/app/Crud/Split/JournalInterface.php index 709f0abd45..2f0727d8e1 100644 --- a/app/Crud/Split/JournalInterface.php +++ b/app/Crud/Split/JournalInterface.php @@ -21,6 +21,12 @@ use Illuminate\Support\Collection; */ interface JournalInterface { + /** + * @param $journal + * + * @return bool + */ + public function markAsComplete(TransactionJournal $journal); /** * @param array $data diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index d27480b67f..e12666657a 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -65,7 +65,6 @@ class Entry $entry->category = new EntryCategory($journal->categories->first()); $entry->bill = new EntryBill($journal->bill); - // TODO support split journals $sources = TransactionJournal::sourceAccountList($journal); $entry->sourceAccount = new EntryAccount($sources->first()); $destinations = TransactionJournal::destinationAccountList($journal); diff --git a/app/Handlers/Events/ConnectJournalToPiggyBank.php b/app/Handlers/Events/ConnectJournalToPiggyBank.php index 7ac02550ba..83089674cc 100644 --- a/app/Handlers/Events/ConnectJournalToPiggyBank.php +++ b/app/Handlers/Events/ConnectJournalToPiggyBank.php @@ -44,7 +44,6 @@ class ConnectJournalToPiggyBank $amount = TransactionJournal::amountPositive($journal); // if piggy account matches source account, the amount is positive - // TODO support split journals if ($piggyBank->account_id == TransactionJournal::sourceAccount($journal)->id) { $amount = bcmul($amount, '-1'); } diff --git a/app/Helpers/Csv/Data.php b/app/Helpers/Csv/Data.php index 781d8ee5cf..b799a6baef 100644 --- a/app/Helpers/Csv/Data.php +++ b/app/Helpers/Csv/Data.php @@ -72,7 +72,7 @@ class Data } /** - * FIXME may return null + * FIXxME may return null * * @return string */ @@ -92,7 +92,7 @@ class Data } /** - * FIXME may return null + * FIXxME may return null * * @return string */ @@ -112,7 +112,7 @@ class Data } /** - * FIXME may return null + * FIXxME may return null * * @return string */ diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index 9117ef8216..c551eef6cf 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -100,38 +100,6 @@ class ReportHelper implements ReportHelperInterface return $collection; } - /** - * Find all transactions and IF we have spent money in them - * with either transactions or journals. - * - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return Collection - */ - public function getCategoriesWithTransactions(Carbon $start, Carbon $end, Collection $accounts): Collection - { - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - $categories = $repository->getCategories(); - $return = new Collection; - foreach ($categories as $category) { - $lastUseDate = $repository->lastUseDate($category, $accounts); - if ($lastUseDate >= $start && $lastUseDate <= $end) { - $return->push($category); - } - } - - $return = $return->sortBy( - function (Category $category) { - return $category->name; - } - ); - - return $return; - } - /** * @param Carbon $start * @param Carbon $end diff --git a/app/Helpers/Report/ReportHelperInterface.php b/app/Helpers/Report/ReportHelperInterface.php index dd30dc7165..33be9fe4e8 100644 --- a/app/Helpers/Report/ReportHelperInterface.php +++ b/app/Helpers/Report/ReportHelperInterface.php @@ -32,15 +32,6 @@ interface ReportHelperInterface */ public function getBillReport(Carbon $start, Carbon $end, Collection $accounts): BillCollection; - /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return Collection - */ - public function getCategoriesWithTransactions(Carbon $start, Carbon $end, Collection $accounts): Collection; - /** * @param Carbon $start * @param Carbon $end diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index c2b9675ca5..872323b6cb 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -218,59 +218,6 @@ class CategoryController extends Controller } - /** - * @param Category $category - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return \Illuminate\Http\JsonResponse - */ - public function period(Category $category, Carbon $start, Carbon $end, Collection $accounts) - { - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($accounts); - $cache->addProperty($category->id); - $cache->addProperty('category'); - $cache->addProperty('period'); - if ($cache->has()) { - return Response::json($cache->get()); - } - - /** @var CRI $repository */ - $repository = app(CRI::class); - $categoryCollection = new Collection([$category]); - // loop over period, add by users range: - $current = clone $start; - $viewRange = Preferences::get('viewRange', '1M')->data; - $format = strval(trans('config.month')); - $set = new Collection; - while ($current < $end) { - $currentStart = clone $current; - $currentEnd = Navigation::endOfPeriod($currentStart, $viewRange); - $spent = $repository->spentInPeriod($categoryCollection, $accounts, $currentStart, $currentEnd); - $earned = $repository->earnedInPeriod($categoryCollection, $accounts, $currentStart, $currentEnd); - - $entry = [ - $category->name, - $currentStart->formatLocalized($format), - $spent, - $earned, - - ]; - $set->push($entry); - $currentEnd->addDay(); - $current = clone $currentEnd; - } - $data = $this->generator->period($set); - $cache->store($data); - - return Response::json($data); - } - /** * @param CRI $repository * @param Category $category diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 24e318e979..da40deee89 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -314,9 +314,6 @@ class ReportController extends Controller // find the budgets we've spent money on this period with these accounts: $budgets = $this->budgetHelper->getBudgetsWithExpenses($start, $end, $accounts); - // find the categories we've spent money on this period with these accounts: - $categories = $this->helper->getCategoriesWithTransactions($start, $end, $accounts); - Session::flash('gaEventCategory', 'report'); Session::flash('gaEventAction', 'year'); Session::flash('gaEventLabel', $start->format('Y')); @@ -333,7 +330,7 @@ class ReportController extends Controller 'reports.default.year', compact( 'start', 'accountReport', 'incomes', 'reportType', 'accountIds', 'end', - 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags', 'budgets', 'categories' + 'expenses', 'incomeTopLength', 'expenseTopLength', 'tags', 'budgets' ) ); } diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 46bd9577ae..896717c6e5 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -31,7 +31,6 @@ use View; * * @package FireflyIII\Http\Controllers\Transaction * - * TODO support piggy banks */ class SplitController extends Controller { @@ -132,9 +131,7 @@ class SplitController extends Controller $repository->storeTransaction($journal, $transaction); } - // TODO move to repository - $journal->completed = 1; - $journal->save(); + $repository->markAsComplete($journal); Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); Preferences::mark(); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 0015bc99a6..31b6bf183d 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -18,6 +18,7 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Requests\JournalFormRequest; use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; +use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; @@ -165,13 +166,11 @@ class TransactionController extends Controller 'amount' => TransactionJournal::amountPositive($journal), ]; - // TODO support split withdrawal - if ($journal->isWithdrawal() && TransactionJournal::destinationAccountTypeStr($journal) == 'Cash account') { + if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { $preFilled['destination_account_name'] = ''; } - // TODO support split withdrawal - if ($journal->isDeposit() && TransactionJournal::sourceAccountTypeStr($journal) == 'Cash account') { + if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) { $preFilled['source_account_name'] = ''; } diff --git a/app/Http/routes.php b/app/Http/routes.php index ede270be5f..a4411ff875 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -210,7 +210,6 @@ Route::group( // categories: Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']); - Route::get('/chart/category/period/{category}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@period']); // these three charts are for reports: Route::get('/chart/category/multi-year/default/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear']); diff --git a/app/Models/Account.php b/app/Models/Account.php index eaddfd0637..2c2b46f911 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -164,7 +164,7 @@ class Account extends Model } /** - * FIXME can return null + * FIxxME can return null * * @param $value * diff --git a/app/Repositories/ExportJob/ExportJobRepository.php b/app/Repositories/ExportJob/ExportJobRepository.php index a5d53b630b..5262926168 100644 --- a/app/Repositories/ExportJob/ExportJobRepository.php +++ b/app/Repositories/ExportJob/ExportJobRepository.php @@ -91,9 +91,6 @@ class ExportJobRepository implements ExportJobRepositoryInterface } /** - * - * FIXME this may return null - * * @param string $key * * @return ExportJob|null diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index a719b7e654..e9605021f3 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -64,7 +64,7 @@ class RuleRepository implements RuleRepositoryInterface } /** - * FIXME can return null + * FIxXME can return null * * @return RuleGroup */ diff --git a/app/Repositories/Shared/ComponentRepository.php b/app/Repositories/Shared/ComponentRepository.php deleted file mode 100644 index 683121338b..0000000000 --- a/app/Repositories/Shared/ComponentRepository.php +++ /dev/null @@ -1,63 +0,0 @@ -pluck('id')->toArray(); - $entry = $object->transactionjournals() - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->whereIn('accounts.id', $ids) - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) - ->before($end) - ->after($start) - ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); - $amount = $entry->journalAmount ?? '0'; - - // all balances based on individual transactions (at the moment, it's an "or or"): - $entry = $object - ->transactions() - // left join journals to get some meta-information. - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - // also left join transaction types so we can do the same type of filtering. - ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') - // need to do these manually. - ->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) - ->whereIn('transactions.account_id', $ids) - ->first([DB::raw('SUM(`transactions`.`amount`) as `journalAmount`')]); - - // sum of amount: - $extraAmount = $entry->journalAmount ?? '0'; - $result = bcadd($amount, $extraAmount); - - return $result; - } -} diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 864df83367..c376c755e6 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -229,11 +229,9 @@ class TagRepository implements TagRepositoryInterface // $checkAccount is the source_account for a withdrawal // $checkAccount is the destination_account for a deposit - // TODO match split journals if ($check->isWithdrawal() && TransactionJournal::sourceAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) { $match = false; } - // TODO match split journals if ($check->isDeposit() && TransactionJournal::destinationAccount($check)->id != TransactionJournal::destinationAccount($journal)->id) { $match = false; } diff --git a/app/Rules/Triggers/FromAccountContains.php b/app/Rules/Triggers/FromAccountContains.php index fe2abb7e4f..58799b60d4 100644 --- a/app/Rules/Triggers/FromAccountContains.php +++ b/app/Rules/Triggers/FromAccountContains.php @@ -52,7 +52,6 @@ final class FromAccountContains extends AbstractTrigger implements TriggerInterf */ public function triggered(TransactionJournal $journal): bool { - // TODO support split withdrawals $fromAccountName = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $search = strtolower($this->triggerValue); $strpos = strpos($fromAccountName, $search); diff --git a/app/Rules/Triggers/FromAccountEnds.php b/app/Rules/Triggers/FromAccountEnds.php index a588742988..96e83fadcf 100644 --- a/app/Rules/Triggers/FromAccountEnds.php +++ b/app/Rules/Triggers/FromAccountEnds.php @@ -52,7 +52,6 @@ final class FromAccountEnds extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - // TODO support split withdrawals $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $nameLength = strlen($name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/FromAccountIs.php b/app/Rules/Triggers/FromAccountIs.php index fb4aae7006..061044cf7d 100644 --- a/app/Rules/Triggers/FromAccountIs.php +++ b/app/Rules/Triggers/FromAccountIs.php @@ -52,7 +52,6 @@ final class FromAccountIs extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - // TODO support split withdrawals $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/FromAccountStarts.php b/app/Rules/Triggers/FromAccountStarts.php index f53a64ba28..62eaafac67 100644 --- a/app/Rules/Triggers/FromAccountStarts.php +++ b/app/Rules/Triggers/FromAccountStarts.php @@ -52,7 +52,6 @@ final class FromAccountStarts extends AbstractTrigger implements TriggerInterfac */ public function triggered(TransactionJournal $journal): bool { - // TODO support split withdrawals $name = strtolower($journal->source_account_name ?? TransactionJournal::sourceAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/ToAccountContains.php b/app/Rules/Triggers/ToAccountContains.php index 12e138dcdd..f6d7c31c68 100644 --- a/app/Rules/Triggers/ToAccountContains.php +++ b/app/Rules/Triggers/ToAccountContains.php @@ -52,7 +52,6 @@ final class ToAccountContains extends AbstractTrigger implements TriggerInterfac */ public function triggered(TransactionJournal $journal): bool { - // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $search = strtolower($this->triggerValue); $strpos = strpos($toAccountName, $search); diff --git a/app/Rules/Triggers/ToAccountEnds.php b/app/Rules/Triggers/ToAccountEnds.php index 220730ac50..f01d8d8ba5 100644 --- a/app/Rules/Triggers/ToAccountEnds.php +++ b/app/Rules/Triggers/ToAccountEnds.php @@ -52,7 +52,6 @@ final class ToAccountEnds extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $toAccountNameLength = strlen($toAccountName); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/ToAccountIs.php b/app/Rules/Triggers/ToAccountIs.php index 8a2f46ea14..6d1ea0181e 100644 --- a/app/Rules/Triggers/ToAccountIs.php +++ b/app/Rules/Triggers/ToAccountIs.php @@ -52,7 +52,6 @@ final class ToAccountIs extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/app/Rules/Triggers/ToAccountStarts.php b/app/Rules/Triggers/ToAccountStarts.php index 64bebd5ce1..8f00f80d0c 100644 --- a/app/Rules/Triggers/ToAccountStarts.php +++ b/app/Rules/Triggers/ToAccountStarts.php @@ -52,7 +52,6 @@ final class ToAccountStarts extends AbstractTrigger implements TriggerInterface */ public function triggered(TransactionJournal $journal): bool { - // TODO support split journals $toAccountName = strtolower($journal->destination_account_name ?? TransactionJournal::destinationAccount($journal)->name); $search = strtolower($this->triggerValue); diff --git a/public/js/ff/boxes.js b/public/js/ff/boxes.js deleted file mode 100644 index ae787e7f5b..0000000000 --- a/public/js/ff/boxes.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * boxes.js - * Copyright (C) 2016 thegrumpydictator@gmail.com - * - * This software may be modified and distributed under the terms - * of the MIT license. See the LICENSE file for details. - */ - -$(function () { - "use strict"; - $('button[data-widget="collapse"]').click(storeBoxState); - - // restore boxes to their original state: - $.each($('.box'), function (i, v) { - var box = $(v); - if (box.attr('id')) { - var state = getBoxState(box.attr('id')); - console.log('Box ' + box.attr('id') + ' should be ' + state); - if(state == 'closed') { - $('button[data-widget="collapse"]', box).click(); - } - } - }); -}); - -function storeBoxState(e) { - "use strict"; - //Find the box parent - var button = $(e.target); - var box = button.parents(".box").first(); - var id = box.attr('id'); - if (id) { - console.log('Box has id: ' + id); - if (box.hasClass('collapsed-box')) { - setBoxState(id, 'open'); - console.log('Box "' + id + '" is now opening / open.'); - } else { - setBoxState(id, 'closed'); - console.log('Box "' + id + '" is now closing / closed.'); - } - } -} - -function setBoxState(id, state) { - "use strict"; - var index = 'ff-box-state-' + id; - if (typeof(Storage) !== "undefined") { - localStorage.setItem(index, state); - } -} -function getBoxState(id) { - "use strict"; - var index = 'ff-box-state-' + id; - if (typeof(Storage) !== "undefined") { - var state = localStorage.getItem(index); - if (state) { - return state; - } - } - return 'open'; -} \ No newline at end of file diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js index 22d6797f08..f342c7e1dc 100644 --- a/public/js/ff/reports/default/year.js +++ b/public/js/ff/reports/default/year.js @@ -27,15 +27,6 @@ function drawChart() { columnChart('chart/budget/period/' + budgetId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, id); }); - - // and another loop - $.each($('.category_year_chart'), function (i, v) { - var holder = $(v); - var id = holder.attr('id'); - var categoryId = holder.data('category'); - columnChart('chart/category/period/' + categoryId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, id); - - }); } diff --git a/resources/views/accounts/index.twig b/resources/views/accounts/index.twig index 2310652b71..78c30497da 100644 --- a/resources/views/accounts/index.twig +++ b/resources/views/accounts/index.twig @@ -21,7 +21,6 @@
-