From f38d80cbf5db2a257b1af40be6caa475e18987ce Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 23 Oct 2014 16:42:11 +0200 Subject: [PATCH 01/57] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e0edc8d65c..dbbe1678bf 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "type": "project", "authors": [ { - "name": "Sander Dorigo", + "name": "James Cole", "email": "thegrumpydictator@gmail.com", "homepage": "https://github.com/JC5", "role": "Developer" From b21275363323a321e9f020cf0cdf1061e678462b Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Mon, 27 Oct 2014 21:27:45 +0100 Subject: [PATCH 02/57] A new form field. --- app/lib/Firefly/Form/Form.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/lib/Firefly/Form/Form.php b/app/lib/Firefly/Form/Form.php index 6414fd983e..1b697f01fe 100644 --- a/app/lib/Firefly/Form/Form.php +++ b/app/lib/Firefly/Form/Form.php @@ -29,6 +29,14 @@ class Form return self::ffInput('checkbox', $name, $value, $options); } + /** + * @param $name + * @param null $value + * @param array $options + * + * @return string + * @throws FireflyException + */ public static function ffAmount($name, $value = null, array $options = []) { $options['step'] = 'any'; @@ -37,6 +45,21 @@ class Form } + /** + * @param $name + * @param null $value + * @param array $options + * + * @return string + * @throws FireflyException + */ + public static function ffBalance($name, $value = null, array $options = []) + { + $options['step'] = 'any'; + return self::ffInput('amount', $name, $value, $options); + + } + /** * @param $name * @param null $value From d54832f61f4b80a7aa077fbc69580d308f2956e3 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Mon, 27 Oct 2014 21:28:15 +0100 Subject: [PATCH 03/57] Code formatting. --- .../Firefly/Helper/Controllers/Account.php | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/app/lib/Firefly/Helper/Controllers/Account.php b/app/lib/Firefly/Helper/Controllers/Account.php index a7fb95a02b..93dd3b12d7 100644 --- a/app/lib/Firefly/Helper/Controllers/Account.php +++ b/app/lib/Firefly/Helper/Controllers/Account.php @@ -12,14 +12,17 @@ class Account implements AccountInterface /** * @param \Account $account + * * @return \TransactionJournal|null */ public function openingBalanceTransaction(\Account $account) { return \TransactionJournal::withRelevantData() ->accountIs($account) - ->leftJoin('transaction_types', 'transaction_types.id', '=', - 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transaction_types', 'transaction_types.id', '=', + 'transaction_journals.transaction_type_id' + ) ->where('transaction_types.type', 'Opening balance') ->first(['transaction_journals.*']); } @@ -36,7 +39,7 @@ class Account implements AccountInterface * For now, Firefly simply warns the user of this. * * @param \Account $account - * @param $perPage + * @param $perPage * * @return array|mixed * @throws \Firefly\Exception\FireflyException @@ -110,17 +113,25 @@ class Account implements AccountInterface // statistics (transactions) - $trIn = floatval(\Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) - ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount')); - $trOut = floatval(\Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) - ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount')); + $trIn = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) + ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount') + ); + $trOut = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) + ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount') + ); $trDiff = $trIn + $trOut; // statistics (transfers) - $trfIn = floatval(\Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) - ->transactionTypes(['Transfer'])->sum('transactions.amount')); - $trfOut = floatval(\Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) - ->transactionTypes(['Transfer'])->sum('transactions.amount')); + $trfIn = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) + ->transactionTypes(['Transfer'])->sum('transactions.amount') + ); + $trfOut = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) + ->transactionTypes(['Transfer'])->sum('transactions.amount') + ); $trfDiff = $trfIn + $trfOut; $stats['period'] = [ From 07caeccf688f9540126527e691b07ca2ae607516 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Mon, 27 Oct 2014 21:28:30 +0100 Subject: [PATCH 04/57] Code formatting and a reference to a new form element. --- app/start/global.php | 69 +++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/app/start/global.php b/app/start/global.php index c5098db09d..10109df498 100644 --- a/app/start/global.php +++ b/app/start/global.php @@ -72,30 +72,51 @@ App::down( ); // forms: -\Form::macro('ffText', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffText($name, $value, $options); -}); -\Form::macro('ffSelect', function ($name, array $list = [], $selected = null, array $options = []) { - return \Firefly\Form\Form::ffSelect($name, $list, $selected, $options); -}); -\Form::macro('ffInteger', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffInteger($name, $value, $options); -}); -\Form::macro('ffAmount', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffAmount($name, $value, $options); -}); -\Form::macro('ffDate', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffDate($name, $value, $options); -}); -\Form::macro('ffTags', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffTags($name, $value, $options); -}); -\Form::macro('ffCheckbox',function ($name, $value = 1, $checked = null, $options = []) { - return \Firefly\Form\Form::ffCheckbox($name, $value, $checked, $options); -}); -\Form::macro('ffOptionsList',function ($type, $name) { - return \Firefly\Form\Form::ffOptionsList($type, $name); -}); +\Form::macro( + 'ffText', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffText($name, $value, $options); + } +); +\Form::macro( + 'ffSelect', function ($name, array $list = [], $selected = null, array $options = []) { + return \Firefly\Form\Form::ffSelect($name, $list, $selected, $options); + } +); +\Form::macro( + 'ffInteger', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffInteger($name, $value, $options); + } +); +\Form::macro( + 'ffAmount', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffAmount($name, $value, $options); + } +); +\Form::macro( + 'ffBalance', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffBalance($name, $value, $options); + } +); +\Form::macro( + 'ffDate', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffDate($name, $value, $options); + } +); +\Form::macro( + 'ffTags', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffTags($name, $value, $options); + } +); +\Form::macro( + 'ffCheckbox', function ($name, $value = 1, $checked = null, $options = []) { + return \Firefly\Form\Form::ffCheckbox($name, $value, $checked, $options); + } +); +\Form::macro( + 'ffOptionsList', function ($type, $name) { + return \Firefly\Form\Form::ffOptionsList($type, $name); + } +); /* From ba4ffa44d2965f3ae06da0ec00b030354692b89a Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Mon, 27 Oct 2014 21:28:58 +0100 Subject: [PATCH 05/57] Fixed a spelling error. --- app/views/recurring/edit.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/recurring/edit.blade.php b/app/views/recurring/edit.blade.php index f4be05b370..d9680c93f1 100644 --- a/app/views/recurring/edit.blade.php +++ b/app/views/recurring/edit.blade.php @@ -21,7 +21,7 @@

From 97e7ac40527c15cbae1ca3634c5f0eda2d5dc8df Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 05:58:10 +0100 Subject: [PATCH 06/57] New routes for accounts. --- app/routes.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/routes.php b/app/routes.php index 535b161678..aaa77b637e 100644 --- a/app/routes.php +++ b/app/routes.php @@ -136,15 +136,13 @@ Route::group(['before' => 'auth'], function () { Route::get('/jump/{range}',['uses' => 'HomeController@rangeJump','as' => 'rangeJump']); // account controller: - Route::get('/accounts', ['uses' => 'AccountController@index', 'as' => 'accounts.index']); - Route::get('/accounts/asset', ['uses' => 'AccountController@asset', 'as' => 'accounts.asset']); - Route::get('/accounts/expense', ['uses' => 'AccountController@expense', 'as' => 'accounts.expense']); - Route::get('/accounts/revenue', ['uses' => 'AccountController@revenue', 'as' => 'accounts.revenue']); - + Route::get('/accounts/json/{what}', ['uses' => 'AccountController@json', 'as' => 'accounts.json'])->where('what','revenue|asset|expense'); + Route::get('/accounts/{what}', ['uses' => 'AccountController@index', 'as' => 'accounts.index'])->where('what','revenue|asset|expense'); Route::get('/accounts/create/{what}', ['uses' => 'AccountController@create', 'as' => 'accounts.create'])->where('what','revenue|asset|expense'); - Route::get('/accounts/{account}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); - Route::get('/accounts/{account}/edit', ['uses' => 'AccountController@edit', 'as' => 'accounts.edit']); - Route::get('/accounts/{account}/delete', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']); + 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}',['uses' => 'AccountController@show','as' => 'accounts.show']); + Route::get('/accounts/sankey/{account}',['uses' => 'AccountController@sankey','as' => 'accounts.sankey']); // budget controller: Route::get('/budgets/date',['uses' => 'BudgetController@indexByDate','as' => 'budgets.index.date']); From d84d88cc10be3e9bdcea7dc1f737466b2602f3c4 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 05:58:32 +0100 Subject: [PATCH 07/57] Completely revamped the account controller. --- app/controllers/AccountController.php | 424 +++++++++++++++++++------- 1 file changed, 315 insertions(+), 109 deletions(-) diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 2ddcd82e42..a2828c4556 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -1,31 +1,229 @@ _accounts = $accounts; - $this->_repository = $repository; View::share('mainTitleIcon', 'fa-credit-card'); View::share('title', 'Accounts'); } + /** + * @param string $what + * + * @return View + * @throws FireflyException + */ + public function index($what = 'default') + { + switch ($what) { + default: + throw new FireflyException('Cannot handle account type "' . e($what) . '".'); + break; + case 'asset': + View::share('subTitleIcon', 'fa-money'); + View::share('subTitle', 'Asset accounts'); + break; + case 'expense': + View::share('subTitleIcon', 'fa-shopping-cart'); + View::share('subTitle', 'Expense accounts'); + break; + case 'revenue': + View::share('subTitleIcon', 'fa-download'); + View::share('subTitle', 'Revenue accounts'); + break; + } + return View::make('accounts.index')->with('what', $what); + } + + + /** + * @param string $what + * + * @return \Illuminate\Http\JsonResponse + * @throws FireflyException + */ + public function json($what = 'default') + { + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Shared\Json\Json $json */ + $json = App::make('FireflyIII\Shared\Json\Json'); + + $parameters = $json->dataTableParameters(); + + switch ($what) { + default: + throw new FireflyException('Cannot handle account type "' . e($what) . '".'); + break; + case 'asset': + $accounts = $acct->getAssetAccounts($parameters); + $count = $acct->countAssetAccounts(); + break; + case 'expense': + $accounts = $acct->getExpenseAccounts($parameters); + $count = $acct->countExpenseAccounts(); + break; + case 'revenue': + $accounts = $acct->getRevenueAccounts($parameters); + $count = $acct->countRevenueAccounts(); + break; + } + + /* + * Output the set compatible with data tables: + */ + $return = [ + 'draw' => intval(Input::get('draw')), + 'recordsTotal' => $count, + 'recordsFiltered' => $accounts->count(), + 'data' => [], + ]; + + /* + * Loop the accounts: + */ + /** @var \Account $account */ + foreach ($accounts as $account) { + $entry = [ + 'name' => ['name' => $account->name, 'url' => route('accounts.show', $account->id)], + 'balance' => $account->balance(), + 'id' => [ + 'edit' => route('accounts.edit', $account->id), + 'delete' => route('accounts.delete', $account->id), + ] + ]; + $return['data'][] = $entry; + } + + + return Response::jsoN($return); + } + + public function transactions(Account $account) { + /* + * TODO get the JSON helper to get transactions or something. + */ + + } + + /** + * @param $account + * + * @return \Illuminate\View\View + */ + public function sankey($account) + { + + /* + * Get the stuff. + */ + $start = Session::get('start'); + $end = Session::get('end'); + $query = \TransactionJournal::withRelevantData() + ->defaultSorting() + ->accountIs($account) + ->after($start) + ->before($end); + $set = $query->get(['transaction_journals.*']); + /* + * Arrays we need: + */ + $collection = []; + $filtered = []; + $result = []; + /** @var \TransactionJournal $entry */ + foreach ($set as $entry) { + switch ($entry->transactionType->type) { + case 'Withdrawal': + /** @var Budget $budget */ + $budget = isset($entry->budgets[0]) ? $entry->budgets[0] : null; + $from = $entry->transactions[0]->account->name; + $amount = floatval($entry->transactions[1]->amount); + if ($budget) { + $to = $budget->name; + } else { + $to = '(no budget)'; + } + $collection[] = [$from, $to, $amount]; + + // also make one for the budget: + $from = $to; + $category = $entry->categories()->first(); + if ($category) { + $to = $category->name . ' (cat)'; + } else { + $to = '(no category)'; + } + $collection[] = [$from, $to, $amount]; + break; + } + } + + /* + * To break "cycles", aka money going back AND forth Firefly searches for previously existing + * key sets (in reversed order) and if we find them, fix it. + * + * If the from-to amount found is larger than the amount going back, the amount going back + * is removed and substracted from the current amount. + * + * If the from-to amount found is less than the amount going back, the entry is ignored + * but substracted from the amount going back. + */ + foreach ($collection as $current) { + list($from, $to, $amount) = $current; + $key = $from . $to; + $reversed = $to . $from; + if (!isset($result[$reversed])) { + if (isset($result[$key])) { + $filtered[$key]['amount'] += $amount; + } else { + $filtered[$key] = ['from' => $from, 'to' => $to, 'amount' => $amount]; + } + } else { + /* + * If there is one, see which one will make it: + */ + $otherAmount = $result[$reversed]['amount']; + if ($amount >= $otherAmount) { + unset($result[$reversed]); + $amount = $amount - $otherAmount; + // set: + if (isset($result[$key])) { + $filtered[$key]['amount'] += $amount; + } else { + $filtered[$key] = ['from' => $from, 'to' => $to, 'amount' => $amount]; + } + } else { + $filtered[$reversed]['amount'] -= $amount; + } + } + + } + ksort($filtered); + + /* + * Collect amounts to give the labels the proper + */ + + /* + * Loop it again to add the amounts. + */ + return View::make('accounts.sankey', compact('filtered')); + } + + /** * @return \Illuminate\View\View */ @@ -43,52 +241,7 @@ class AccountController extends \BaseController break; } - - return View::make('accounts.create')->with('subTitle', 'Create a new ' . $what . ' account')->with( - 'what', $what - ); - } - - /** - * @return $this - */ - public function asset() - { - View::share('subTitleIcon', 'fa-money'); - - $accounts = $this->_repository->getOfTypes(['Asset account', 'Default account']); - - return View::make('accounts.asset')->with('subTitle', 'Asset accounts')->with( - 'accounts', $accounts - ); - } - - /** - * @return $this - */ - public function expense() - { - View::share('subTitleIcon', 'fa-shopping-cart'); - - $accounts = $this->_repository->getOfTypes(['Expense account', 'Beneficiary account']); - - return View::make('accounts.expense')->with('subTitle', 'Expense accounts')->with( - 'accounts', $accounts - ); - } - - /** - * @return $this - */ - public function revenue() - { - View::share('subTitleIcon', 'fa-download'); - - $accounts = $this->_repository->getOfTypes(['Revenue account']); - - return View::make('accounts.revenue')->with('subTitle', 'Revenue accounts')->with( - 'accounts', $accounts - ); + return View::make('accounts.create')->with('subTitle', 'Create a new ' . $what . ' account')->with('what', $what); } /** @@ -99,9 +252,9 @@ class AccountController extends \BaseController public function delete(Account $account) { return View::make('accounts.delete')->with('account', $account) - ->with( - 'subTitle', 'Delete ' . strtolower($account->accountType->type) . ' "' . $account->name . '"' - ); + ->with( + 'subTitle', 'Delete ' . strtolower($account->accountType->type) . ' "' . $account->name . '"' + ); } /** @@ -111,20 +264,67 @@ class AccountController extends \BaseController */ public function destroy(Account $account) { + $type = $account->accountType->type; - $this->_repository->destroy($account); + + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Database\TransactionJournal $jrnls */ + $jrnls = App::make('FireflyIII\Database\TransactionJournal'); + + /* + * Find the "initial balance account", should it exist: + */ + $initialBalance = $acct->findInitialBalanceAccount($account); + + /* + * Get all the transaction journals that are part of this/these account(s): + */ + $journals = []; + if ($initialBalance) { + $transactions = $initialBalance->transactions()->get(); + /** @var \Transaction $transaction */ + foreach ($transactions as $transaction) { + $journals[] = $transaction->transaction_journal_id; + } + } + /** @var \Transaction $transaction */ + foreach ($account->transactions() as $transaction) { + $journals[] = $transaction->transaction_journal_id; + } + + $journals = array_unique($journals); + + /* + * Delete the journals. Should get rid of the transactions as well. + */ + foreach ($journals as $id) { + $journal = $jrnls->find($id); + $journal->delete(); + } + + /* + * Delete it + */ + if ($initialBalance) { + $acct->destroy($initialBalance); + } + + $acct->destroy($account); + Session::flash('success', 'The account was deleted.'); switch ($type) { case 'Asset account': case 'Default account': - return Redirect::route('accounts.asset'); + return Redirect::route('accounts.index', 'asset'); break; case 'Expense account': case 'Beneficiary account': - return Redirect::route('accounts.expense'); + return Redirect::route('accounts.index', 'expense'); break; case 'Revenue account': - return Redirect::route('accounts.revenue'); + return Redirect::route('accounts.index', 'revenue'); break; } @@ -153,18 +353,23 @@ class AccountController extends \BaseController break; } - $openingBalance = $this->_accounts->openingBalanceTransaction($account); - return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance) + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); - ->with('subTitle', 'Edit ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'); - } + $openingBalance = $acct->openingBalanceTransaction($account); + Session::forget('prefilled'); + if (!is_null($openingBalance)) { + $prefilled['openingbalancedate'] = $openingBalance->date->format('Y-m-d'); + $prefilled['openingbalance'] = floatval($openingBalance->transactions()->where('account_id', $account->id)->first()->amount); + Session::flash('prefilled', $prefilled); + } - /** - * @return $this - */ - public function index() - { - return View::make('error')->with('message', 'This view has been disabled'); + + return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance)->with( + 'subTitle', 'Edit ' . strtolower( + $account->accountType->type + ) . ' "' . $account->name . '"' + ); } /** @@ -189,54 +394,55 @@ class AccountController extends \BaseController } - $data = $this->_accounts->show($account, 40); - - return View::make('accounts.show')->with('account', $account)->with('show', $data)->with( - 'subTitle', - 'Details for ' . strtolower($account->accountType->type) . ' "' . $account->name . '"' - ); + //$data = $this->_accounts->show($account, 40); + return View::make('accounts.show') + ->with('account', $account) + ->with('subTitle', 'Details for ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'); } /** * @return $this|\Illuminate\Http\RedirectResponse + * @throws FireflyException */ public function store() { $data = Input::all(); $data['what'] = isset($data['what']) && $data['what'] != '' ? $data['what'] : 'asset'; + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); - - switch ($data['what']) { + switch ($data['post_submit_action']) { default: - case 'asset': - $data['account_type'] = 'Asset account'; - break; - case 'expense': - $data['account_type'] = 'Expense account'; - break; - case 'revenue': - $data['account_type'] = 'Revenue account'; + throw new FireflyException('Cannot handle post_submit_action "' . e($data['post_submit_action']) . '"'); break; + case 'create_another': + case 'store': + $messages = $acct->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save account: ' . $messages['errors']->first()); + return Redirect::route('accounts.create', $data['what'])->withInput()->withErrors($messages['errors']); + } + // store! + $acct->store($data); + Session::flash('success', 'New account stored!'); - } - $account = $this->_repository->store($data); - - if ($account->validate()) { - // saved! return to wherever. - Session::flash('success', 'Account "' . $account->name . '" created!'); - if (intval(Input::get('create')) === 1) { + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('accounts.create', $data['what']); + } else { + return Redirect::route('accounts.index', $data['what']); + } + break; + case 'validate_only': + $messageBags = $acct->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); return Redirect::route('accounts.create', $data['what'])->withInput(); - } else { - - return Redirect::route('accounts.' . e($data['what'])); - } - } else { - // did not save, return with error: - Session::flash('error', 'Could not save the new account: ' . $account->errors()->first()); - - return Redirect::route('accounts.create', $data['what'])->withErrors($account->errors())->withInput(); - + break; } } From 899f61671f8ade0b21d19a8ae2b0642a0bb2cce4 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 05:58:48 +0100 Subject: [PATCH 08/57] All new library set for the account controller (and others). --- app/lib/FireflyIII/Database/Account.php | 426 ++++++++++++++++++ .../FireflyIII/Database/AccountInterface.php | 109 +++++ app/lib/FireflyIII/Database/AccountType.php | 120 +++++ .../Database/AccountTypeInterface.php | 20 + app/lib/FireflyIII/Database/CUD.php | 49 ++ .../Database/CommonDatabaseCalls.php | 47 ++ app/lib/FireflyIII/Database/SwitchUser.php | 32 ++ app/lib/FireflyIII/Database/Transaction.php | 178 ++++++++ .../Database/TransactionCurrency.php | 112 +++++ .../Database/TransactionCurrencyInterface.php | 26 ++ .../Database/TransactionInterface.php | 20 + .../Database/TransactionJournal.php | 253 +++++++++++ .../Database/TransactionJournalInterface.php | 19 + .../FireflyIII/Database/TransactionType.php | 111 +++++ .../Database/TransactionTypeInterface.php | 20 + .../Exception/NotImplementedException.php | 20 + app/lib/FireflyIII/Shared/Json/Account.php | 14 + app/lib/FireflyIII/Shared/Json/Json.php | 86 ++++ 18 files changed, 1662 insertions(+) create mode 100644 app/lib/FireflyIII/Database/Account.php create mode 100644 app/lib/FireflyIII/Database/AccountInterface.php create mode 100644 app/lib/FireflyIII/Database/AccountType.php create mode 100644 app/lib/FireflyIII/Database/AccountTypeInterface.php create mode 100644 app/lib/FireflyIII/Database/CUD.php create mode 100644 app/lib/FireflyIII/Database/CommonDatabaseCalls.php create mode 100644 app/lib/FireflyIII/Database/SwitchUser.php create mode 100644 app/lib/FireflyIII/Database/Transaction.php create mode 100644 app/lib/FireflyIII/Database/TransactionCurrency.php create mode 100644 app/lib/FireflyIII/Database/TransactionCurrencyInterface.php create mode 100644 app/lib/FireflyIII/Database/TransactionInterface.php create mode 100644 app/lib/FireflyIII/Database/TransactionJournal.php create mode 100644 app/lib/FireflyIII/Database/TransactionJournalInterface.php create mode 100644 app/lib/FireflyIII/Database/TransactionType.php create mode 100644 app/lib/FireflyIII/Database/TransactionTypeInterface.php create mode 100644 app/lib/FireflyIII/Exception/NotImplementedException.php create mode 100644 app/lib/FireflyIII/Shared/Json/Account.php create mode 100644 app/lib/FireflyIII/Shared/Json/Json.php diff --git a/app/lib/FireflyIII/Database/Account.php b/app/lib/FireflyIII/Database/Account.php new file mode 100644 index 0000000000..70eac86228 --- /dev/null +++ b/app/lib/FireflyIII/Database/Account.php @@ -0,0 +1,426 @@ +setUser(\Auth::user()); + } + + /** + * Get all asset accounts. Optional JSON based parameters. + * + * @param array $parameters + * + * @return Collection + */ + public function getAssetAccounts(array $parameters = []) + { + return $this->getAccountsByType(['Default account', 'Asset account'], $parameters); + + } + + /** + * @param \Account $account + * + * @return \Account|null + */ + public function findInitialBalanceAccount(\Account $account) + { + /** @var \FireflyIII\Database\AccountType $acctType */ + $acctType = \App::make('FireflyIII\Database\AccountType'); + + $accountType = $acctType->findByWhat('initial'); + + return $this->getUser()->accounts()->where('account_type_id', $accountType->id)->where('name', 'LIKE', $account->name . '%')->first(); + } + + /** + * @param array $types + * @param array $parameters + * + * @return Collection + */ + public function getAccountsByType(array $types, array $parameters = []) + { + /* + * Basic query: + */ + $query = $this->getUser()->accounts()->accountTypeIn($types); + + + /* + * Without an opening balance, the rest of these queries will fail. + */ + + $query->leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id'); + $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'); + + /* + * Not used, but useful for the balance within a certain month / year. + */ + $balanceOnDate = isset($parameters['date']) ? $parameters['date'] : Carbon::now(); + $query->where( + function ($q) use ($balanceOnDate) { + $q->where('transaction_journals.date', '<=', $balanceOnDate->format('Y-m-d')); + $q->orWhereNull('transaction_journals.date'); + } + ); + + $query->groupBy('accounts.id'); + + /* + * If present, process parameters for sorting: + */ + if (isset($parameters['order'])) { + foreach ($parameters['order'] as $instr) { + $query->orderBy($instr['name'], $instr['dir']); + } + } + + /* + * If present, process parameters for searching. + */ + if (isset($parameters['search'])) { + $query->where('name', 'LIKE', '%' . e($parameters['search']['value'] . '%')); + } + + /* + * If present, start at $start: + */ + if (isset($parameters['start'])) { + $query->skip(intval($parameters['start'])); + } + if (isset($parameters['length'])) { + $query->take(intval($parameters['length'])); + } + + return $query->get(['accounts.*', \DB::Raw('SUM(`transactions`.`amount`) as `balance`')]); + } + + /** + * @return int + */ + public function countAssetAccounts() + { + return $this->countAccountsByType(['Default account', 'Asset account']); + } + + /** + * @return int + */ + public function countExpenseAccounts() + { + return $this->countAccountsByType(['Expense account', 'Beneficiary account']); + } + + /** + * @param array $types + * + * @return int + */ + public function countAccountsByType(array $types) + { + return $this->getUser()->accounts()->accountTypeIn($types)->count(); + } + + /** + * @param array $parameters + * + * @return Collection + */ + public function getExpenseAccounts(array $parameters = []) + { + return $this->getAccountsByType(['Expense account', 'Beneficiary account'], $parameters); + } + + /** + * Get all default accounts. + * + * @return Collection + */ + public function getDefaultAccounts() + { + // TODO: Implement getDefaultAccounts() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * Counts the number of total revenue accounts. Useful for DataTables. + * + * @return int + */ + public function countRevenueAccounts() + { + return $this->countAccountsByType(['Revenue account']); + } + + /** + * Get all revenue accounts. + * + * @param array $parameters + * + * @return Collection + */ + public function getRevenueAccounts(array $parameters = []) + { + return $this->getAccountsByType(['Revenue account'], $parameters); + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + $model->delete(); + return true; + + } + + /** + * @param \Account $account + * + * @return \TransactionJournal|null + */ + public function openingBalanceTransaction(\Account $account) + { + return \TransactionJournal::withRelevantData() + ->accountIs($account) + ->leftJoin( + 'transaction_types', 'transaction_types.id', '=', + 'transaction_journals.transaction_type_id' + ) + ->where('transaction_types.type', 'Opening balance') + ->first(['transaction_journals.*']); + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + die('No impl'); + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + $warnings = new MessageBag; + $successes = new MessageBag; + $errors = new MessageBag; + + /* + * Name validation: + */ + if (!isset($model['name'])) { + $errors->add('name', 'Name is mandatory'); + } + + if (isset($model['name']) && strlen($model['name']) == 0) { + $errors->add('name', 'Name is too short'); + } + if (isset($model['name']) && strlen($model['name']) > 100) { + $errors->add('name', 'Name is too long'); + } + $validator = \Validator::make([$model], \Account::$rules); + if ($validator->invalid()) { + $errors->merge($errors); + } + + /* + * type validation. + */ + if (!isset($model['what'])) { + $errors->add('name', 'Internal error: need to know type of account!'); + } + + /* + * Opening balance and opening balance date. + */ + if (isset($model['what']) && $model['what'] == 'asset') { + if (isset($model['openingbalance']) && strlen($model['openingbalance']) > 0 && !is_numeric($model['openingbalance'])) { + $errors->add('openingbalance', 'This is not a number.'); + } + if (isset($model['openingbalancedate']) && strlen($model['openingbalancedate']) > 0) { + try { + new Carbon($model['openingbalancedate']); + } catch (\Exception $e) { + $errors->add('openingbalancedate', 'This date is invalid.'); + } + } + } + + + if (!$errors->has('name')) { + $successes->add('name', 'OK'); + } + if (!$errors->has('openingbalance')) { + $successes->add('openingbalance', 'OK'); + } + if (!$errors->has('openingbalancedate')) { + $successes->add('openingbalancedate', 'OK'); + } + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + + /* + * Find account type. + */ + /** @var \FireflyIII\Database\AccountType $acctType */ + $acctType = \App::make('FireflyIII\Database\AccountType'); + + $accountType = $acctType->findByWhat($data['what']); + + $data['user_id'] = $this->getUser()->id; + $data['account_type_id'] = $accountType->id; + $data['active'] = isset($data['active']) && $data['active'] === '1' ? 1 : 0; + + + $data = array_except($data, array('_token', 'what')); + $account = new \Account($data); + if (!$account->validate()) { + var_dump($account->errors()->all()); + exit; + } + $account->save(); + if (isset($data['openingbalance']) && floatval($data['openingbalance']) != 0) { + $this->storeInitialBalance($account, $data); + } + + + /* Tell transaction journal to store a new one.*/ + + + return $account; + + } + + /** + * @param \Account $account + * @param array $data + * + * @return bool + */ + public function storeInitialBalance(\Account $account, array $data) + { + $opposingData = [ + 'name' => $account->name . ' Initial Balance', + 'active' => 0, + 'what' => 'initial' + ]; + $opposingAccount = $this->store($opposingData); + + /* + * Create a journal from opposing to account or vice versa. + */ + $balance = floatval($data['openingbalance']); + $date = new Carbon($data['openingbalancedate']); + /** @var \FireflyIII\Database\TransactionJournal $tj */ + $tj = \App::make('FireflyIII\Database\TransactionJournal'); + if ($balance < 0) { + // first transaction draws money from the new account to the opposing + $from = $account; + $to = $opposingAccount; + } else { + // first transaction puts money into account + $from = $opposingAccount; + $to = $account; + } + + // data for transaction journal: + $balance = $balance < 0 ? $balance * -1 : $balance; + $opening = [ + 'what' => 'opening', + 'currency' => 'EUR', + 'amount' => $balance, + 'from' => $from, + 'to' => $to, + 'date' => $date, + 'description' => 'Opening balance for new account ' . $account->name, + ]; + + + $validation = $tj->validate($opening); + if ($validation['errors']->count() == 0) { + $tj->store($opening); + return true; + } else { + var_dump($validation['errors']); + exit; + } + return false; + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/AccountInterface.php b/app/lib/FireflyIII/Database/AccountInterface.php new file mode 100644 index 0000000000..0fcdebdcb1 --- /dev/null +++ b/app/lib/FireflyIII/Database/AccountInterface.php @@ -0,0 +1,109 @@ +first(); + break; + case 'asset': + return \AccountType::whereType('Asset account')->first(); + break; + case 'revenue': + return \AccountType::whereType('Revenue account')->first(); + break; + case 'initial': + return \AccountType::whereType('Initial balance account')->first(); + break; + default: + throw new FireflyException('Cannot find account type described as "' . e($what) . '".'); + break; + + } + return null; + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + // TODO: Implement validate() method. + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/AccountTypeInterface.php b/app/lib/FireflyIII/Database/AccountTypeInterface.php new file mode 100644 index 0000000000..9de34619c4 --- /dev/null +++ b/app/lib/FireflyIII/Database/AccountTypeInterface.php @@ -0,0 +1,20 @@ +_user; + } + + /** + * @param $user + */ + public function setUser($user) + { + $this->_user = $user; + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Transaction.php b/app/lib/FireflyIII/Database/Transaction.php new file mode 100644 index 0000000000..bb10a91b4c --- /dev/null +++ b/app/lib/FireflyIII/Database/Transaction.php @@ -0,0 +1,178 @@ +add('account', 'No account present'); + } + if (isset($model['account']) && !($model['account'] instanceof \Account)) { + $errors->add('account', 'No valid account present'); + } + if (isset($model['account_id']) && intval($model['account_id']) < 0) { + $errors->add('account', 'No valid account_id present'); + } + + if (isset($model['piggybank_id']) && intval($model['piggybank_id']) < 0) { + $errors->add('piggybank', 'No valid piggybank_id present'); + } + + if (!isset($model['transaction_journal_id']) && !isset($model['transaction_journal'])) { + $errors->add('transaction_journal', 'No TJ present'); + } + if (isset($model['transaction_journal']) && !($model['transaction_journal'] instanceof \TransactionJournal)) { + $errors->add('transaction_journal', 'No valid transaction_journal present'); + } + if (isset($model['transaction_journal_id']) && intval($model['transaction_journal_id']) < 0) { + $errors->add('account', 'No valid transaction_journal_id present'); + } + + if (isset($model['description']) && strlen($model['description']) > 255) { + $errors->add('account', 'Description too long'); + } + + if (!isset($model['amount'])) { + $errors->add('amount', 'No amount present.'); + } + if (isset($model['amount']) && floatval($model['amount']) == 0) { + $errors->add('amount', 'Invalid amount.'); + } + + if (!$errors->has('account')) { + $successes->add('account', 'OK'); + } + if (!$errors->has('')) { + $successes->add('piggybank', 'OK'); + } + if (!$errors->has('transaction_journal')) { + $successes->add('transaction_journal', 'OK'); + } + if (!$errors->has('amount')) { + $successes->add('amount', 'OK'); + } + + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + $transaction = new \Transaction; + $transaction->account()->associate($data['account']); + $transaction->transactionJournal()->associate($data['transaction_journal']); + $transaction->amount = floatval($data['amount']); + if (isset($data['piggybank'])) { + $transaction->piggybank()->associate($data['piggybank']); + } + if (isset($data['description'])) { + $transaction->description = $data['description']; + } + if ($transaction->validate()) { + $transaction->save(); + } else { + throw new FireflyException($transaction->errors()->first()); + } + return $transaction; + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionCurrency.php b/app/lib/FireflyIII/Database/TransactionCurrency.php new file mode 100644 index 0000000000..2aeb26af50 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionCurrency.php @@ -0,0 +1,112 @@ +first(); + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionCurrencyInterface.php b/app/lib/FireflyIII/Database/TransactionCurrencyInterface.php new file mode 100644 index 0000000000..5e10785088 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionCurrencyInterface.php @@ -0,0 +1,26 @@ +setUser(\Auth::user()); + } + + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + + $warnings = new MessageBag; + $successes = new MessageBag; + $errors = new MessageBag; + + + if (!isset($model['what'])) { + $errors->add('description', 'Internal error: need to know type of transaction!'); + } + if (isset($model['recurring_transaction_id']) && intval($model['recurring_transaction_id']) < 0) { + $errors->add('recurring_transaction_id', 'Recurring transaction is invalid.'); + } + if (!isset($model['description'])) { + $errors->add('description', 'This field is mandatory.'); + } + if (isset($model['description']) && strlen($model['description']) == 0) { + $errors->add('description', 'This field is mandatory.'); + } + if (isset($model['description']) && strlen($model['description']) > 255) { + $errors->add('description', 'Description is too long.'); + } + + if (!isset($model['currency'])) { + $errors->add('description', 'Internal error: currency is mandatory!'); + } + if (isset($model['date']) && !($model['date'] instanceof Carbon) && strlen($model['date']) > 0) { + try { + new Carbon($model['date']); + } catch (\Exception $e) { + $errors->add('date', 'This date is invalid.'); + } + } + if (!isset($model['date'])) { + $errors->add('date', 'This date is invalid.'); + } + + if (isset($model['to_id']) && intval($model['to_id']) < 0) { + $errors->add('account_to', 'Invalid to-account'); + } + if (isset($model['from_id']) && intval($model['from_id']) < 0) { + $errors->add('account_from', 'Invalid from-account'); + } + if (isset($model['to']) && !($model['to'] instanceof \Account)) { + $errors->add('account_to', 'Invalid to-account'); + } + if (isset($model['from']) && !($model['from'] instanceof \Account)) { + $errors->add('account_from', 'Invalid from-account'); + } + if (!isset($model['amount']) || (isset($model['amount']) && floatval($model['amount']) < 0)) { + $errors->add('amount', 'Invalid amount'); + } + if (!isset($model['from']) && !isset($model['to'])) { + $errors->add('account_to', 'No accounts found!'); + } + + $validator = \Validator::make([$model], \Transaction::$rules); + if ($validator->invalid()) { + $errors->merge($errors); + } + + + /* + * Add "OK" + */ + if (!$errors->has('description')) { + $successes->add('description', 'OK'); + } + if (!$errors->has('date')) { + $successes->add('date', 'OK'); + } + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; + + + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + + /** @var \FireflyIII\Database\TransactionType $typeRepository */ + $typeRepository = \App::make('FireflyIII\Database\TransactionType'); + + /** @var \FireflyIII\Database\TransactionCurrency $currencyRepository */ + $currencyRepository = \App::make('FireflyIII\Database\TransactionCurrency'); + + /** @var \FireflyIII\Database\Transaction $transactionRepository */ + $transactionRepository = \App::make('FireflyIII\Database\Transaction'); + + $journalType = $typeRepository->findByWhat($data['what']); + $currency = $currencyRepository->findByCode($data['currency']); + + $journal = new \TransactionJournal; + $journal->transactionType()->associate($journalType); + $journal->transactionCurrency()->associate($currency); + $journal->user()->associate($this->getUser()); + $journal->description = $data['description']; + $journal->date = $data['date']; + $journal->completed = 0; + //$journal->user_id = $this->getUser()->id; + + /* + * This must be enough to store the journal: + */ + if (!$journal->validate()) { + \Log::error($journal->errors()->all()); + throw new FireflyException('store() transactionjournal failed, but it should not!'); + } + $journal->save(); + + /* + * Then store both transactions. + */ + $first = [ + 'account' => $data['from'], + 'transaction_journal' => $journal, + 'amount' => ($data['amount'] * -1), + ]; + $validate = $transactionRepository->validate($first); + if ($validate['errors']->count() == 0) { + $transactionRepository->store($first); + } else { + throw new FireflyException($validate['errors']->first()); + } + + $second = [ + 'account' => $data['to'], + 'transaction_journal' => $journal, + 'amount' => floatval($data['amount']), + ]; + + $validate = $transactionRepository->validate($second); + if ($validate['errors']->count() == 0) { + $transactionRepository->store($second); + } else { + throw new FireflyException($validate['errors']->first()); + } + + $journal->completed = 1; + $journal->save(); + return $journal; + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + return $this->getUser()->transactionjournals()->find($id); + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionJournalInterface.php b/app/lib/FireflyIII/Database/TransactionJournalInterface.php new file mode 100644 index 0000000000..b791c06ad6 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionJournalInterface.php @@ -0,0 +1,19 @@ +first(); + break; + default: + throw new FireflyException('Cannot find transaction type described as "' . e($what) . '".'); + break; + + } + return null; + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionTypeInterface.php b/app/lib/FireflyIII/Database/TransactionTypeInterface.php new file mode 100644 index 0000000000..a1c4096f41 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionTypeInterface.php @@ -0,0 +1,20 @@ + intval(\Input::get('start')), + 'length' => $length, + 'draw' => intval(\Input::get('draw')), + ]; + + + /* + * Columns: + */ + if (!is_null(\Input::get('columns')) && is_array(\Input::get('columns'))) { + foreach (\Input::get('columns') as $column) { + $parameters['columns'][] = [ + 'data' => $column['data'], + 'name' => $column['name'], + 'searchable' => $column['searchable'] == 'true' ? true : false, + 'orderable' => $column['orderable'] == 'true' ? true : false, + 'search' => [ + 'value' => $column['search']['value'], + 'regex' => $column['search']['regex'] == 'true' ? true : false, + ] + ]; + } + } + + + /* + * Sorting. + */ + $parameters['orderOnAccount'] = false; + if (!is_null(\Input::get('order')) && is_array(\Input::get('order'))) { + foreach (\Input::get('order') as $order) { + $columnIndex = intval($order['column']); + $columnName = $parameters['columns'][$columnIndex]['name']; + $parameters['order'][] = [ + 'name' => $columnName, + 'dir' => strtoupper($order['dir']) + ]; + if ($columnName == 'to' || $columnName == 'from') { + $parameters['orderOnAccount'] = true; + } + } + } + /* + * Search parameters: + */ + $parameters['search'] = [ + 'value' => '', + 'regex' => false + ]; + if (!is_null(\Input::get('search')) && is_array(\Input::get('search'))) { + $search = \Input::get('search'); + $parameters['search'] = [ + 'value' => $search['value'], + 'regex' => $search['regex'] == 'true' ? true : false + ]; + } + return $parameters; + } +} \ No newline at end of file From e5f8db78f9972aa522093b8c472dbc8d8dafcd6f Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 05:59:14 +0100 Subject: [PATCH 09/57] New views and layouts for the account controller. --- app/views/accounts/create.blade.php | 109 ++++--------- app/views/accounts/delete.blade.php | 52 +++--- app/views/accounts/edit.blade.php | 89 ++++------- app/views/accounts/index.blade.php | 79 +++++----- app/views/accounts/sankey.blade.php | 46 ++++++ app/views/accounts/show.blade.php | 139 ++++++++++------ public/assets/javascript/firefly/accounts.js | 158 +++++++++++++++++-- 7 files changed, 406 insertions(+), 266 deletions(-) create mode 100644 app/views/accounts/sankey.blade.php diff --git a/app/views/accounts/create.blade.php b/app/views/accounts/create.blade.php index 6e3c7d2128..0a1c2912d9 100644 --- a/app/views/accounts/create.blade.php +++ b/app/views/accounts/create.blade.php @@ -4,98 +4,53 @@ {{Form::hidden('what',$what)}}
-

Mandatory fields

+
+
+ Mandatory fields +
+
+ {{Form::ffText('name')}} + -
- {{ Form::label('name', 'Account name', ['class' => 'col-sm-4 control-label'])}} -
- {{ Form::text('name', Input::old('name'), ['class' => 'form-control']) }} - @if($errors->has('name')) -

{{$errors->first('name')}}

- @else - @if($what == 'asset') - - Use something descriptive such as "checking account" or "My Bank Main Account". - - @endif - @if($what == 'expense') - - Use something descriptive such as "Albert Heijn" or "Amazon". - - @endif - @if($what == 'revenue') - - Use something descriptive such as "my mom" or "my job". - - @endif - @endif
- +

+ +

+
- @if($what == 'asset') -

Optional fields

-
- {{ Form::label('openingbalance', 'Opening balance', ['class' => 'col-sm-4 control-label'])}} -
-
- - {{Form::input('number','openingbalance', Input::old('openingbalance'), ['step' => 'any', 'class' => 'form-control'])}} -
- @if($errors->has('openingbalance')) -

{{$errors->first('openingbalance')}}

- @else - What's the current balance of this new account? - @endif +
+
+ Optional fields +
+
+ @if($what == 'asset') + {{Form::ffBalance('openingbalance')}} + {{Form::ffDate('openingbalancedate', date('Y-m-d'))}} + @endif + {{Form::ffCheckbox('active','1',true)}}
-
- {{ Form::label('openingbalancedate', 'Opening balance date', ['class' => 'col-sm-4 control-label'])}} -
- {{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: date('Y-m-d'), ['class' - => 'form-control']) }} - @if($errors->has('openingbalancedate')) -

{{$errors->first('openingbalancedate')}}

- @else - When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account. - @endif + + + +
+
+ Options +
+
+ {{Form::ffOptionsList('create','account')}}
- @endif
- -
-
-
- - -
- -
-
- -
-
-
- -
-
- -
-
-
-
- - {{Form::close()}} @stop \ No newline at end of file diff --git a/app/views/accounts/delete.blade.php b/app/views/accounts/delete.blade.php index 7174cb310f..6f2059a15e 100644 --- a/app/views/accounts/delete.blade.php +++ b/app/views/accounts/delete.blade.php @@ -1,44 +1,38 @@ @extends('layouts.default') @section('content') -
-
-

- Remember that deleting something is permanent. -

-
-
- {{Form::open(['class' => 'form-horizontal','url' => route('accounts.destroy',$account->id)])}}
-
- @if($account->transactions()->count() > 0) -

- Account "{{{$account->name}}}" still has {{$account->transactions()->count()}} transaction(s) associated to it. - These will be deleted as well. -

- @endif +
+
+
+ Delete account "{{{$account->name}}}" +
+
+

+ Are you sure? +

-

- Press "Delete permanently" If you are sure you want to delete "{{{$account->name}}}". -

+ @if($account->transactions()->count() > 0) +

+ Account "{{{$account->name}}}" still has {{$account->transactions()->count()}} transaction(s) associated to it. + These will be deleted as well. +

+ @endif + +

+ + Cancel +

+
+
-
- - @if($account->accountType->type == 'Asset account' || $account->accountType->type == 'Default account') - Cancel - @endif - @if($account->accountType->type == 'Expense account' || $account->accountType->type == 'Beneficiary account') - Cancel - @endif - @if($account->accountType->type == 'Revenue account') - Cancel - @endif +
diff --git a/app/views/accounts/edit.blade.php b/app/views/accounts/edit.blade.php index 3393570a0b..eccbd863a0 100644 --- a/app/views/accounts/edit.blade.php +++ b/app/views/accounts/edit.blade.php @@ -11,79 +11,46 @@ {{Form::model($account, ['class' => 'form-horizontal','url' => route('accounts.update',$account->id)])}}
-

Mandatory fields

- -
- {{ Form::label('name', 'Account name', ['class' => 'col-sm-4 control-label'])}} -
- {{ Form::text('name', Input::old('name'), ['class' => 'form-control']) }} - @if($errors->has('name')) -

{{$errors->first('name')}}

- @else - Use something descriptive such as "checking account" or "Albert Heijn". - @endif - +
+
+ Mandatory fields +
+
+ {{Form::ffText('name')}}
- +

+ +

- @if($account->accounttype->type == 'Default account' || $account->accounttype->type == 'Asset account') -

Optional fields

- -
- {{ Form::label('openingbalance', 'Opening balance', ['class' => 'col-sm-4 control-label'])}} -
-
- - @if(!is_null($openingBalance)) - {{Form::input('number','openingbalance', Input::old('openingbalance') ?: $openingBalance->transactions[1]->amount, ['step' => 'any', 'class' => 'form-control'])}} - @else - {{Form::input('number','openingbalance', Input::old('openingbalance'), ['step' => 'any', 'class' => 'form-control'])}} - @endif - -
- - @if($errors->has('openingbalance')) -

{{$errors->first('openingbalance')}}

- @else - What's the current balance of this new account? +
+
+ Optional fields +
+
+ {{Form::ffCheckbox('active','1')}} + @if($account->accounttype->type == 'Default account' || $account->accounttype->type == 'Asset account') + {{Form::ffBalance('openingbalance')}} + {{Form::ffDate('openingbalancedate')}} @endif
-
- {{ Form::label('openingbalancedate', 'Opening balance date', ['class' => 'col-sm-4 control-label'])}} -
- @if(!is_null($openingBalance)) - {{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: $openingBalance->date->format('Y-m-d'), ['class' => 'form-control']) }} - @else - {{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: '', ['class' => 'form-control']) }} - @endif - @if($errors->has('openingbalancedate')) -

{{$errors->first('openingbalancedate')}}

- @else - When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account. - @endif + + +
+
+ Options +
+
+ {{Form::ffOptionsList('create','account')}}
- @endif
- -
-
-
-
-
- -
-
-
-
- - {{Form::close()}} @stop \ No newline at end of file diff --git a/app/views/accounts/index.blade.php b/app/views/accounts/index.blade.php index 545d3f6fc6..d67f0812e1 100644 --- a/app/views/accounts/index.blade.php +++ b/app/views/accounts/index.blade.php @@ -2,42 +2,49 @@ @section('content')
-

Firefly - Accounts -

-

- Accounts are the record holders for transactions and transfers. Money moves from one account to another. -

-

- In a double-entry bookkeeping system almost everything is an account. Your own personal - bank accounts are representated as accounts (naturally), but the stores you buy stuff at are also - represented as accounts. Likewise, if you have a job, your salary is drawn from their account. -

-

- Create a new account -

+
+
+ {{{$subTitle}}} + + +
+
+ + +
+
+ + +
+
+ + + + + + + + +
NamebalanceID
+
+
-
-
- @if(count($accounts['personal']) > 0) -

Your accounts

-

- These are your personal accounts. -

+@stop +@section('scripts') +{{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} +{{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} + + +@stop - @include('accounts.list',['accounts' => $accounts['personal']]) - @endif - - @if(count($accounts['beneficiaries']) > 0) -

Beneficiaries

-

- These are beneficiaries; places where you spend money or people who pay you. -

- @include('accounts.list',['accounts' => $accounts['beneficiaries']]) - @endif - -
-
- -@stop \ No newline at end of file +@section('styles') +{{HTML::style('assets/stylesheets/datatables/dataTables.bootstrap.css')}} +@endsection \ No newline at end of file diff --git a/app/views/accounts/sankey.blade.php b/app/views/accounts/sankey.blade.php new file mode 100644 index 0000000000..b1a29aae7c --- /dev/null +++ b/app/views/accounts/sankey.blade.php @@ -0,0 +1,46 @@ +@extends('layouts.default') +@section('content') + + +
+ + + + +@stop +@section('scripts') + + +@stop \ No newline at end of file diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index f3148e26f8..e066edc09e 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -4,68 +4,86 @@
- {{{$account->name}}} + {{{$account->name}}}
-
+
@include('partials.date_nav') + + +
+
+ Summary +
+
+ {{-- + + + + + + + + + + + + + + + + + + + + + +
Expense / incomeTransfers
Out + {{mf($show['statistics']['period']['out'])}} + + + {{mf($show['statistics']['period']['t_out'])}} + +
In + {{mf($show['statistics']['period']['in'])}} + + + {{mf($show['statistics']['period']['t_in'])}} + +
Difference{{mf($show['statistics']['period']['diff'])}}{{mf($show['statistics']['period']['t_diff'])}}
+ --}} +
+
-
+
- Summary -
-
- - - - - - - - - - - - - - - - - - - - - -
Expense / incomeTransfers
Out - {{mf($show['statistics']['period']['out'])}} - - - {{mf($show['statistics']['period']['t_out'])}} - -
In - {{mf($show['statistics']['period']['in'])}} - - - {{mf($show['statistics']['period']['t_in'])}} - -
Difference{{mf($show['statistics']['period']['diff'])}}{{mf($show['statistics']['period']['t_diff'])}}
-
-
-
-
-
-
- Related + Transaction
+ + + + + + + + + + + + + +
DateDescriptionAmount (€)FromToBudget / categoryID
+ + {{-- @if(count($show['statistics']['accounts']) > 0) @@ -98,30 +116,49 @@ @endif
+ --}}
+
+
+
+
+ Transaction +
+
+ +
+
+
+
+ +{{--
- -

Transactions For selected account and period

@include('paginated.transactions',['journals' => $show['journals'],'sum' => true])
- +--}} @stop @section('styles') {{HTML::style('assets/stylesheets/highslide/highslide.css')}} +{{HTML::style('assets/stylesheets/datatables/dataTables.bootstrap.css')}} @stop @section('scripts') +{{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} +{{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} +{{HTML::script('assets/javascript/datatables/transactions.js')}} {{HTML::script('assets/javascript/highcharts/highcharts.js')}} {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file diff --git a/public/assets/javascript/firefly/accounts.js b/public/assets/javascript/firefly/accounts.js index a5fc3b60f3..a6ba2bdb98 100644 --- a/public/assets/javascript/firefly/accounts.js +++ b/public/assets/javascript/firefly/accounts.js @@ -1,12 +1,72 @@ $(function () { -if($('#chart').length == 1) { - /** - * get data from controller for home charts: - */ + if ($('#accountTable').length == 1) { + drawDatatable(); + } + //if ($('#accountTransactionsTable').length == 1) { + // drawTransactionsForAccount(); + //} + if ($('#transactionByAccountTable').length == 1) { + renderTransactionsFromURL(URL, container); + } +}); + +function drawDatatable() { + var opt = { + serverSide: true, + ajax: URL, + paging: true, + processing: true, + columns: [ + { + name: 'name', + data: 'name', + searchable: true, + render: function (data) { + return '' + data.name + ''; + } + + }, + { + name: 'balance', + data: 'balance', + title: 'Amount (\u20AC)', + searchable: false, + sortable: true, + render: function (data) { + var amount = parseInt(data); + if (amount < 0) { + '\u20AC ' + data.toFixed(2) + '' + } + if (amount > 0) { + '\u20AC ' + data.toFixed(2) + '' + } + return '\u20AC ' + data.toFixed(2) + '' + } + }, + { + name: 'id', + data: 'id', + title: '', + render: function (data) { + return ''; + } + } + ] + }; + $('#accountTable').DataTable(opt); +} + + +function drawTransactionsForAccount() { $.getJSON('chart/home/account/' + accountID).success(function (data) { var options = { chart: { - renderTo: 'chart', + renderTo: 'accountTransactionsTable', type: 'spline' }, @@ -18,7 +78,7 @@ if($('#chart').length == 1) { allowDecimals: false, labels: { formatter: function () { - if(this.value >= 1000 || this.value <= -1000) { + if (this.value >= 1000 || this.value <= -1000) { return '\u20AC ' + (this.value / 1000) + 'k'; } return '\u20AC ' + this.value; @@ -37,10 +97,10 @@ if($('#chart').length == 1) { text: null } }, - legend: {enabled:false}, + legend: {enabled: false}, tooltip: { formatter: function () { - return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y,2); + return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); } }, plotOptions: { @@ -68,10 +128,84 @@ if($('#chart').length == 1) { enabled: false } }; - $('#chart').highcharts(options); + $('#accountTransactionsTable').highcharts(options); }); } - - -}); \ No newline at end of file +//$(function () { +//if($('#chart').length == 1) { +// /** +// * get data from controller for home charts: +// */ +// $.getJSON('chart/home/account/' + accountID).success(function (data) { +// var options = { +// chart: { +// renderTo: 'chart', +// type: 'spline' +// }, +// +// series: data.series, +// title: { +// text: null +// }, +// yAxis: { +// allowDecimals: false, +// labels: { +// formatter: function () { +// if(this.value >= 1000 || this.value <= -1000) { +// return '\u20AC ' + (this.value / 1000) + 'k'; +// } +// return '\u20AC ' + this.value; +// +// } +// }, +// title: {text: null} +// }, +// xAxis: { +// type: 'datetime', +// dateTimeLabelFormats: { +// day: '%e %b', +// week: '%e %b' +// }, +// title: { +// text: null +// } +// }, +// legend: {enabled:false}, +// tooltip: { +// formatter: function () { +// return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y,2); +// } +// }, +// plotOptions: { +// line: { +// shadow: true +// }, +// series: { +// cursor: 'pointer', +// negativeColor: '#FF0000', +// threshold: 0, +// lineWidth: 1, +// marker: { +// radius: 0 +// }, +// point: { +// events: { +// click: function (e) { +// alert('click!'); +// } +// } +// } +// } +// }, +// credits: { +// enabled: false +// } +// }; +// $('#chart').highcharts(options); +// }); +//} +// +// +// +//}); \ No newline at end of file From 4533b46436d3ef522ab3688e59235675f545138b Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 06:00:41 +0100 Subject: [PATCH 10/57] Updated menu logic --- app/views/partials/menu.blade.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/partials/menu.blade.php b/app/views/partials/menu.blade.php index 8bbbbae2bc..eba87f6edb 100644 --- a/app/views/partials/menu.blade.php +++ b/app/views/partials/menu.blade.php @@ -98,13 +98,13 @@ Accounts @@ -208,7 +208,7 @@
  • From a0afa25145dacc8ed989487a9dc317c959025111 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 09:25:29 +0100 Subject: [PATCH 11/57] Cleanup and prep-work for new charts (will be a new feature). --- app/controllers/AccountController.php | 17 ++-- app/routes.php | 2 +- app/views/accounts/sankey.blade.php | 6 +- app/views/accounts/show.blade.php | 43 +++++---- public/assets/javascript/firefly/accounts.js | 92 ++------------------ 5 files changed, 46 insertions(+), 114 deletions(-) diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index a2828c4556..2b8f17f3dc 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -111,7 +111,8 @@ class AccountController extends BaseController return Response::jsoN($return); } - public function transactions(Account $account) { + public function transactions(Account $account) + { /* * TODO get the JSON helper to get transactions or something. */ @@ -123,7 +124,7 @@ class AccountController extends BaseController * * @return \Illuminate\View\View */ - public function sankey($account) + public function sankeyOut($account) { /* @@ -162,7 +163,7 @@ class AccountController extends BaseController $from = $to; $category = $entry->categories()->first(); if ($category) { - $to = $category->name . ' (cat)'; + $to = ' ' . $category->name; } else { $to = '(no category)'; } @@ -211,16 +212,18 @@ class AccountController extends BaseController } } - ksort($filtered); - /* - * Collect amounts to give the labels the proper + * Take out the keys: */ + foreach ($filtered as $key => $entry) { + $result[] = [$entry['from'],$entry['to'],$entry['amount']]; + } + /* * Loop it again to add the amounts. */ - return View::make('accounts.sankey', compact('filtered')); + return Response::json($result); } diff --git a/app/routes.php b/app/routes.php index aaa77b637e..52d8c728e9 100644 --- a/app/routes.php +++ b/app/routes.php @@ -142,7 +142,7 @@ Route::group(['before' => 'auth'], function () { 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}',['uses' => 'AccountController@show','as' => 'accounts.show']); - Route::get('/accounts/sankey/{account}',['uses' => 'AccountController@sankey','as' => 'accounts.sankey']); + Route::get('/accounts/sankey/{account}/out',['uses' => 'AccountController@sankeyOut','as' => 'accounts.sankey.out']); // budget controller: Route::get('/budgets/date',['uses' => 'BudgetController@indexByDate','as' => 'budgets.index.date']); diff --git a/app/views/accounts/sankey.blade.php b/app/views/accounts/sankey.blade.php index b1a29aae7c..1246408a11 100644 --- a/app/views/accounts/sankey.blade.php +++ b/app/views/accounts/sankey.blade.php @@ -29,12 +29,10 @@ google.setOnLoadCallback(drawChart); // Set chart options var options = { - width: 600, - sankey: { link: { color: { fill: '#9fa8da', fillOpacity: 0.8 } }, - node: { color: { fill: '#a61d4c' }, - label: { color: '#871b47' } } + node: { color: { fill: '#000' }, + label: { color: '#000' } } } }; diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index e066edc09e..ed164b3028 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -7,7 +7,7 @@ {{{$account->name}}}
  • -
    +
    @@ -61,11 +61,36 @@
    + + +
    +
    +
    +
    + Out +
    +
    +
    +
    +
    +
    +
    +
    +
    + In +
    +
    + +
    +
    +
    +
    +
    - Transaction + Transactions
    @@ -122,18 +147,7 @@
    -
    -
    -
    -
    - Transaction -
    -
    -
    -
    -
    -
    {{--
    @@ -153,12 +167,9 @@ @section('scripts') {{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} {{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} -{{HTML::script('assets/javascript/datatables/transactions.js')}} {{HTML::script('assets/javascript/highcharts/highcharts.js')}} {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file diff --git a/public/assets/javascript/firefly/accounts.js b/public/assets/javascript/firefly/accounts.js index a6ba2bdb98..33d0b22040 100644 --- a/public/assets/javascript/firefly/accounts.js +++ b/public/assets/javascript/firefly/accounts.js @@ -2,12 +2,10 @@ $(function () { if ($('#accountTable').length == 1) { drawDatatable(); } - //if ($('#accountTransactionsTable').length == 1) { - // drawTransactionsForAccount(); - //} - if ($('#transactionByAccountTable').length == 1) { - renderTransactionsFromURL(URL, container); + if ($('#overviewChart').length == 1) { + drawOverviewChart(); } + }); function drawDatatable() { @@ -62,11 +60,11 @@ function drawDatatable() { } -function drawTransactionsForAccount() { +function drawOverviewChart() { $.getJSON('chart/home/account/' + accountID).success(function (data) { var options = { chart: { - renderTo: 'accountTransactionsTable', + renderTo: 'overviewChart', type: 'spline' }, @@ -128,84 +126,6 @@ function drawTransactionsForAccount() { enabled: false } }; - $('#accountTransactionsTable').highcharts(options); + $('#overviewChart').highcharts(options); }); } - -//$(function () { -//if($('#chart').length == 1) { -// /** -// * get data from controller for home charts: -// */ -// $.getJSON('chart/home/account/' + accountID).success(function (data) { -// var options = { -// chart: { -// renderTo: 'chart', -// type: 'spline' -// }, -// -// series: data.series, -// title: { -// text: null -// }, -// yAxis: { -// allowDecimals: false, -// labels: { -// formatter: function () { -// if(this.value >= 1000 || this.value <= -1000) { -// return '\u20AC ' + (this.value / 1000) + 'k'; -// } -// return '\u20AC ' + this.value; -// -// } -// }, -// title: {text: null} -// }, -// xAxis: { -// type: 'datetime', -// dateTimeLabelFormats: { -// day: '%e %b', -// week: '%e %b' -// }, -// title: { -// text: null -// } -// }, -// legend: {enabled:false}, -// tooltip: { -// formatter: function () { -// return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y,2); -// } -// }, -// plotOptions: { -// line: { -// shadow: true -// }, -// series: { -// cursor: 'pointer', -// negativeColor: '#FF0000', -// threshold: 0, -// lineWidth: 1, -// marker: { -// radius: 0 -// }, -// point: { -// events: { -// click: function (e) { -// alert('click!'); -// } -// } -// } -// } -// }, -// credits: { -// enabled: false -// } -// }; -// $('#chart').highcharts(options); -// }); -//} -// -// -// -//}); \ No newline at end of file From c323942d926257546aa2c5001839b003f11b6a36 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 09:27:36 +0100 Subject: [PATCH 12/57] Add placeholder. --- app/views/accounts/show.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index ed164b3028..f6bdcab975 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -70,7 +70,7 @@ Out
    -
    +
    @@ -80,7 +80,7 @@ In
    - +
    From aeb2c7deebabe7442f24eb4f3ba06456b0028a2e Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 09:27:46 +0100 Subject: [PATCH 13/57] Remove function. --- app/controllers/AccountController.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 2b8f17f3dc..37ecfaeee4 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -110,15 +110,6 @@ class AccountController extends BaseController return Response::jsoN($return); } - - public function transactions(Account $account) - { - /* - * TODO get the JSON helper to get transactions or something. - */ - - } - /** * @param $account * From 4d4b62a7661a799174156b478c326edc1e8036df Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 09:28:14 +0100 Subject: [PATCH 14/57] Add todo-text --- app/views/accounts/show.blade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index f6bdcab975..195225ed9a 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -21,6 +21,7 @@ Summary
    + On the todo list. {{-- From 633328a965de15c3a84540b9005d1ec6eab788ac Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 09:29:47 +0100 Subject: [PATCH 15/57] Removed references to sankey. --- app/controllers/AccountController.php | 107 -------------------------- app/routes.php | 1 - 2 files changed, 108 deletions(-) diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 37ecfaeee4..8b3b4d4795 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -110,113 +110,6 @@ class AccountController extends BaseController return Response::jsoN($return); } - /** - * @param $account - * - * @return \Illuminate\View\View - */ - public function sankeyOut($account) - { - - /* - * Get the stuff. - */ - $start = Session::get('start'); - $end = Session::get('end'); - $query = \TransactionJournal::withRelevantData() - ->defaultSorting() - ->accountIs($account) - ->after($start) - ->before($end); - $set = $query->get(['transaction_journals.*']); - /* - * Arrays we need: - */ - $collection = []; - $filtered = []; - $result = []; - /** @var \TransactionJournal $entry */ - foreach ($set as $entry) { - switch ($entry->transactionType->type) { - case 'Withdrawal': - /** @var Budget $budget */ - $budget = isset($entry->budgets[0]) ? $entry->budgets[0] : null; - $from = $entry->transactions[0]->account->name; - $amount = floatval($entry->transactions[1]->amount); - if ($budget) { - $to = $budget->name; - } else { - $to = '(no budget)'; - } - $collection[] = [$from, $to, $amount]; - - // also make one for the budget: - $from = $to; - $category = $entry->categories()->first(); - if ($category) { - $to = ' ' . $category->name; - } else { - $to = '(no category)'; - } - $collection[] = [$from, $to, $amount]; - break; - } - } - - /* - * To break "cycles", aka money going back AND forth Firefly searches for previously existing - * key sets (in reversed order) and if we find them, fix it. - * - * If the from-to amount found is larger than the amount going back, the amount going back - * is removed and substracted from the current amount. - * - * If the from-to amount found is less than the amount going back, the entry is ignored - * but substracted from the amount going back. - */ - foreach ($collection as $current) { - list($from, $to, $amount) = $current; - $key = $from . $to; - $reversed = $to . $from; - if (!isset($result[$reversed])) { - if (isset($result[$key])) { - $filtered[$key]['amount'] += $amount; - } else { - $filtered[$key] = ['from' => $from, 'to' => $to, 'amount' => $amount]; - } - } else { - /* - * If there is one, see which one will make it: - */ - $otherAmount = $result[$reversed]['amount']; - if ($amount >= $otherAmount) { - unset($result[$reversed]); - $amount = $amount - $otherAmount; - // set: - if (isset($result[$key])) { - $filtered[$key]['amount'] += $amount; - } else { - $filtered[$key] = ['from' => $from, 'to' => $to, 'amount' => $amount]; - } - } else { - $filtered[$reversed]['amount'] -= $amount; - } - } - - } - /* - * Take out the keys: - */ - foreach ($filtered as $key => $entry) { - $result[] = [$entry['from'],$entry['to'],$entry['amount']]; - } - - - /* - * Loop it again to add the amounts. - */ - return Response::json($result); - } - /** * @return \Illuminate\View\View diff --git a/app/routes.php b/app/routes.php index 52d8c728e9..22d86fa854 100644 --- a/app/routes.php +++ b/app/routes.php @@ -142,7 +142,6 @@ Route::group(['before' => 'auth'], function () { 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}',['uses' => 'AccountController@show','as' => 'accounts.show']); - Route::get('/accounts/sankey/{account}/out',['uses' => 'AccountController@sankeyOut','as' => 'accounts.sankey.out']); // budget controller: Route::get('/budgets/date',['uses' => 'BudgetController@indexByDate','as' => 'budgets.index.date']); From 004488d45301324b2a1a4c7172aded3509759a40 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 11:10:40 +0100 Subject: [PATCH 16/57] New todo items. --- app/views/accounts/asset.blade.php | 2 +- app/views/accounts/create.blade.php | 3 --- app/views/accounts/expense.blade.php | 2 +- app/views/accounts/list.blade.php | 2 +- app/views/accounts/revenue.blade.php | 2 +- app/views/accounts/sankey.blade.php | 2 +- app/views/budgets/create.blade.php | 2 +- app/views/budgets/delete.blade.php | 2 +- app/views/budgets/edit.blade.php | 2 +- app/views/budgets/indexByBudget.blade.php | 2 +- app/views/budgets/indexByDate.blade.php | 2 +- app/views/budgets/nobudget.blade.php | 2 +- app/views/budgets/show.blade.php | 2 +- app/views/categories/create.blade.php | 2 +- app/views/categories/delete.blade.php | 2 +- app/views/categories/edit.blade.php | 2 +- app/views/categories/index.blade.php | 2 +- app/views/categories/show.blade.php | 2 +- app/views/charts/info.blade.php | 2 +- app/views/emails/auth/reminder.blade.php | 1 + .../emails/user/{remindme-text.php => remindme-text.blade.php} | 0 app/views/layouts/guest.blade.php | 2 +- app/views/limits/create.blade.php | 2 +- app/views/limits/delete.blade.php | 2 +- app/views/limits/edit.blade.php | 2 +- app/views/lists/transactions.blade.php | 2 +- app/views/paginated/transactions.blade.php | 2 +- app/views/piggybanks/create-piggybank.blade.php | 2 +- app/views/piggybanks/create-repeated.blade.php | 2 +- app/views/piggybanks/delete.blade.php | 2 +- app/views/piggybanks/edit-piggybank.blade.php | 2 +- app/views/piggybanks/edit-repeated.blade.php | 2 +- app/views/piggybanks/index.blade.php | 2 +- app/views/piggybanks/modifyAmount.blade.php | 2 +- app/views/piggybanks/show.blade.php | 2 +- app/views/preferences/index.blade.php | 2 +- app/views/reports/index.blade.php | 2 +- app/views/search/index.blade.php | 2 +- app/views/start.blade.php | 2 +- 39 files changed, 37 insertions(+), 39 deletions(-) rename app/views/emails/user/{remindme-text.php => remindme-text.blade.php} (100%) diff --git a/app/views/accounts/asset.blade.php b/app/views/accounts/asset.blade.php index 5cc72b31c9..b64988ce02 100644 --- a/app/views/accounts/asset.blade.php +++ b/app/views/accounts/asset.blade.php @@ -13,6 +13,6 @@ @endif - + @stop \ No newline at end of file diff --git a/app/views/accounts/create.blade.php b/app/views/accounts/create.blade.php index 0a1c2912d9..edf174bce6 100644 --- a/app/views/accounts/create.blade.php +++ b/app/views/accounts/create.blade.php @@ -10,9 +10,6 @@
    {{Form::ffText('name')}} - - -

    diff --git a/app/views/accounts/expense.blade.php b/app/views/accounts/expense.blade.php index d64ef24f63..0002fba73e 100644 --- a/app/views/accounts/expense.blade.php +++ b/app/views/accounts/expense.blade.php @@ -21,7 +21,7 @@ Create a new expense account

    @endif - + diff --git a/app/views/accounts/list.blade.php b/app/views/accounts/list.blade.php index 7ba7ceed8d..52bef1acf3 100644 --- a/app/views/accounts/list.blade.php +++ b/app/views/accounts/list.blade.php @@ -23,4 +23,4 @@
    @endforeach -
    \ No newline at end of file + \ No newline at end of file diff --git a/app/views/accounts/revenue.blade.php b/app/views/accounts/revenue.blade.php index d32e2e5655..c5a1aed311 100644 --- a/app/views/accounts/revenue.blade.php +++ b/app/views/accounts/revenue.blade.php @@ -24,5 +24,5 @@
    - + @stop \ No newline at end of file diff --git a/app/views/accounts/sankey.blade.php b/app/views/accounts/sankey.blade.php index 1246408a11..26beb8fe60 100644 --- a/app/views/accounts/sankey.blade.php +++ b/app/views/accounts/sankey.blade.php @@ -40,5 +40,5 @@ google.setOnLoadCallback(drawChart); var chart = new google.visualization.Sankey(document.getElementById('sankey_multiple')); chart.draw(data, options); } - + @stop \ No newline at end of file diff --git a/app/views/budgets/create.blade.php b/app/views/budgets/create.blade.php index 96d6664410..b15dc00408 100644 --- a/app/views/budgets/create.blade.php +++ b/app/views/budgets/create.blade.php @@ -13,7 +13,7 @@ end of the [period], congratulations! You have saved money!

    -
    +
    {{Form::open(['class' => 'form-horizontal','url' => route('budgets.store')])}} diff --git a/app/views/budgets/delete.blade.php b/app/views/budgets/delete.blade.php index 78abbcc3f7..4f352f1238 100644 --- a/app/views/budgets/delete.blade.php +++ b/app/views/budgets/delete.blade.php @@ -25,7 +25,7 @@

    - +
    diff --git a/app/views/budgets/edit.blade.php b/app/views/budgets/edit.blade.php index ecf8564944..f179965866 100644 --- a/app/views/budgets/edit.blade.php +++ b/app/views/budgets/edit.blade.php @@ -28,7 +28,7 @@
    -
    +
    diff --git a/app/views/budgets/indexByBudget.blade.php b/app/views/budgets/indexByBudget.blade.php index c4596e92a6..b567fc482a 100644 --- a/app/views/budgets/indexByBudget.blade.php +++ b/app/views/budgets/indexByBudget.blade.php @@ -25,7 +25,7 @@

    - +
    diff --git a/app/views/budgets/indexByDate.blade.php b/app/views/budgets/indexByDate.blade.php index a953a8a633..705dbb7105 100644 --- a/app/views/budgets/indexByDate.blade.php +++ b/app/views/budgets/indexByDate.blade.php @@ -23,7 +23,7 @@ Create a new envelope - + diff --git a/app/views/budgets/nobudget.blade.php b/app/views/budgets/nobudget.blade.php index 293d8cdab1..9f392c4eb8 100644 --- a/app/views/budgets/nobudget.blade.php +++ b/app/views/budgets/nobudget.blade.php @@ -9,7 +9,7 @@ and {{Session::get('end')->format('d M Y')}}.

    @endif - + @if($transactions->count() > 0)
    diff --git a/app/views/budgets/show.blade.php b/app/views/budgets/show.blade.php index 5be4b68fa6..0d2f43f3d2 100644 --- a/app/views/budgets/show.blade.php +++ b/app/views/budgets/show.blade.php @@ -1,6 +1,6 @@ @extends('layouts.default') @section('content') -
    +

    Budgets can help you cut back on spending.

    diff --git a/app/views/categories/create.blade.php b/app/views/categories/create.blade.php index f97cab4f67..bf8c855596 100644 --- a/app/views/categories/create.blade.php +++ b/app/views/categories/create.blade.php @@ -8,7 +8,7 @@ Expenses grouped in categories do not have to reoccur every month or every week, like budgets.

    -
    +
    {{Form::open(['class' => 'form-horizontal','url' => route('categories.store')])}} diff --git a/app/views/categories/delete.blade.php b/app/views/categories/delete.blade.php index fb262aedea..9524245af1 100644 --- a/app/views/categories/delete.blade.php +++ b/app/views/categories/delete.blade.php @@ -6,7 +6,7 @@ Remember that deleting something is permanent.

    - + {{Form::open(['class' => 'form-horizontal','url' => route('categories.destroy',$category->id)])}}
    diff --git a/app/views/categories/edit.blade.php b/app/views/categories/edit.blade.php index f245bf4757..d747576b34 100644 --- a/app/views/categories/edit.blade.php +++ b/app/views/categories/edit.blade.php @@ -4,7 +4,7 @@

    Use categories to group your expenses

    -
    + {{Form::open(['class' => 'form-horizontal','url' => route('categories.update',$category->id)])}} diff --git a/app/views/categories/index.blade.php b/app/views/categories/index.blade.php index f6bced7af0..78a2451a1f 100644 --- a/app/views/categories/index.blade.php +++ b/app/views/categories/index.blade.php @@ -11,7 +11,7 @@ Create a new category

    - +
    diff --git a/app/views/categories/show.blade.php b/app/views/categories/show.blade.php index cb5c54418f..1d7d549d0b 100644 --- a/app/views/categories/show.blade.php +++ b/app/views/categories/show.blade.php @@ -15,7 +15,7 @@ transactions for the currently selected period.

    -
    + @include('partials.date_nav') diff --git a/app/views/charts/info.blade.php b/app/views/charts/info.blade.php index dbe16970f1..00e590e129 100644 --- a/app/views/charts/info.blade.php +++ b/app/views/charts/info.blade.php @@ -9,4 +9,4 @@ @endforeach -
    {{mf($entry['amount']*-1)}}
    \ No newline at end of file + \ No newline at end of file diff --git a/app/views/emails/auth/reminder.blade.php b/app/views/emails/auth/reminder.blade.php index aebea9e364..276f8c59f2 100644 --- a/app/views/emails/auth/reminder.blade.php +++ b/app/views/emails/auth/reminder.blade.php @@ -12,3 +12,4 @@
    + \ No newline at end of file diff --git a/app/views/emails/user/remindme-text.php b/app/views/emails/user/remindme-text.blade.php similarity index 100% rename from app/views/emails/user/remindme-text.php rename to app/views/emails/user/remindme-text.blade.php diff --git a/app/views/layouts/guest.blade.php b/app/views/layouts/guest.blade.php index e775f43325..df2a721548 100644 --- a/app/views/layouts/guest.blade.php +++ b/app/views/layouts/guest.blade.php @@ -25,4 +25,4 @@ @yield('content')
    - \ No newline at end of file + \ No newline at end of file diff --git a/app/views/limits/create.blade.php b/app/views/limits/create.blade.php index 6b660ead47..a3a13c4dcf 100644 --- a/app/views/limits/create.blade.php +++ b/app/views/limits/create.blade.php @@ -14,7 +14,7 @@ always add more of them.

    - + {{Form::open(['class' => 'form-horizontal','url' => route('budgets.limits.store',$prefilled['budget_id'])])}} {{Form::hidden('from',e(Input::get('from')))}} diff --git a/app/views/limits/delete.blade.php b/app/views/limits/delete.blade.php index 631405dd93..c3cffd29d7 100644 --- a/app/views/limits/delete.blade.php +++ b/app/views/limits/delete.blade.php @@ -6,7 +6,7 @@ - + {{Form::open(['class' => 'form-horizontal','url' => route('budgets.limits.destroy',$limit->id)])}} diff --git a/app/views/limits/edit.blade.php b/app/views/limits/edit.blade.php index e9f2fb90b3..7ba95b132b 100644 --- a/app/views/limits/edit.blade.php +++ b/app/views/limits/edit.blade.php @@ -17,7 +17,7 @@ @endif

    - + {{Form::open(['class' => 'form-horizontal','url' => route('budgets.limits.update',$limit->id)])}} {{Form::hidden('from',e(Input::get('from')))}} diff --git a/app/views/lists/transactions.blade.php b/app/views/lists/transactions.blade.php index 8ce89718cd..ceb0f54757 100644 --- a/app/views/lists/transactions.blade.php +++ b/app/views/lists/transactions.blade.php @@ -2,7 +2,7 @@ A - Date + Date Description Amount (€) From diff --git a/app/views/paginated/transactions.blade.php b/app/views/paginated/transactions.blade.php index 31980183f4..223541781e 100644 --- a/app/views/paginated/transactions.blade.php +++ b/app/views/paginated/transactions.blade.php @@ -1,3 +1,3 @@ {{$journals->links()}} @include('lists.transactions') -{{$journals->links()}} \ No newline at end of file +{{$journals->links()}} \ No newline at end of file diff --git a/app/views/piggybanks/create-piggybank.blade.php b/app/views/piggybanks/create-piggybank.blade.php index 8dbbd137db..5fb1eb65d4 100644 --- a/app/views/piggybanks/create-piggybank.blade.php +++ b/app/views/piggybanks/create-piggybank.blade.php @@ -4,7 +4,7 @@

    Use piggy banks to save for a one-time goal.

    - + {{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.store.piggybank')])}} diff --git a/app/views/piggybanks/create-repeated.blade.php b/app/views/piggybanks/create-repeated.blade.php index df51394373..cd55d693a0 100644 --- a/app/views/piggybanks/create-repeated.blade.php +++ b/app/views/piggybanks/create-repeated.blade.php @@ -3,7 +3,7 @@

    Create repeated expenses to keep track of long-term planned expenses

    -
    +
    {{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.store.repeated')])}} diff --git a/app/views/piggybanks/delete.blade.php b/app/views/piggybanks/delete.blade.php index 29255e2188..4541ea09d7 100644 --- a/app/views/piggybanks/delete.blade.php +++ b/app/views/piggybanks/delete.blade.php @@ -5,7 +5,7 @@

    Remember that deleting something is permanent.

    - + {{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.destroy',$piggybank->id)])}} diff --git a/app/views/piggybanks/edit-piggybank.blade.php b/app/views/piggybanks/edit-piggybank.blade.php index 876aac0204..ec572047a3 100644 --- a/app/views/piggybanks/edit-piggybank.blade.php +++ b/app/views/piggybanks/edit-piggybank.blade.php @@ -4,7 +4,7 @@

    Use piggy banks to save for a one-time goal.

    - + {{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.update',$piggybank->id)])}} diff --git a/app/views/piggybanks/edit-repeated.blade.php b/app/views/piggybanks/edit-repeated.blade.php index 6caa2a4312..a16fe00add 100644 --- a/app/views/piggybanks/edit-repeated.blade.php +++ b/app/views/piggybanks/edit-repeated.blade.php @@ -5,7 +5,7 @@

    Create repeated expenses to keep track of long-term planned expenses

    - + {{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.update',$piggybank->id)])}}
    diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index bb4af677cf..78dde186e1 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -4,7 +4,7 @@

    diff --git a/app/views/piggybanks/modifyAmount.blade.php b/app/views/piggybanks/modifyAmount.blade.php index 776022d1a9..6a4b5e4ca9 100644 --- a/app/views/piggybanks/modifyAmount.blade.php +++ b/app/views/piggybanks/modifyAmount.blade.php @@ -7,7 +7,7 @@ Remove money from "{{{$piggybank->name}}}" @endif -

    +
    {{Form::token()}} diff --git a/app/views/piggybanks/show.blade.php b/app/views/piggybanks/show.blade.php index 90c84704e2..1f5faf4aa9 100644 --- a/app/views/piggybanks/show.blade.php +++ b/app/views/piggybanks/show.blade.php @@ -15,7 +15,7 @@ @endif - +

    General information

    diff --git a/app/views/preferences/index.blade.php b/app/views/preferences/index.blade.php index f9afac5501..b9b273a565 100644 --- a/app/views/preferences/index.blade.php +++ b/app/views/preferences/index.blade.php @@ -3,7 +3,7 @@ {{Form::open(['class' => 'form-horizontal'])}} - +
    diff --git a/app/views/reports/index.blade.php b/app/views/reports/index.blade.php index ff79240f6f..888107e0e8 100644 --- a/app/views/reports/index.blade.php +++ b/app/views/reports/index.blade.php @@ -3,7 +3,7 @@

    - Here be content. + Here be content.

    diff --git a/app/views/search/index.blade.php b/app/views/search/index.blade.php index 5ac821defb..c8940ac822 100644 --- a/app/views/search/index.blade.php +++ b/app/views/search/index.blade.php @@ -1,7 +1,7 @@ @extends('layouts.default') @section('content') @if(!is_null($query)) -
    +
    @if(isset($result['transactions']) && $result['transactions']->count() > 0)
    diff --git a/app/views/start.blade.php b/app/views/start.blade.php index 2f1821e78c..907a489bd8 100644 --- a/app/views/start.blade.php +++ b/app/views/start.blade.php @@ -12,7 +12,7 @@

    Welcome to Firefly! To get started, choose either of the two options below. -

    +

    From b1d7a9451ac5e3f5dbb2527faf24f6c7d42e2a19 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 11:25:22 +0100 Subject: [PATCH 17/57] Cleaner piggybank view. --- app/views/piggybanks/index.blade.php | 73 ++++++++++++++++++---------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 78dde186e1..e3cfd2aa0b 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -1,27 +1,45 @@ @extends('layouts.default') @section('content') - +@if($countNonRepeating > 0)
    -
    +@foreach($piggybanks as $piggyBank) + @if($piggyBank->repeats == 0) +
    +
    + +
    +
    +
    +
    +
    +

    + {{mf($piggyBank->currentRelevantRep()->currentamount)}} of {{mf($piggyBank->targetamount)}}
    + @if($piggyBank->targetamount-$piggyBank->currentRelevantRep()->currentamount > 0) + {{mf($piggyBank->targetamount-$piggyBank->currentRelevantRep()->currentamount)}} to go. + @endif +

    + + +
    +
    +
    + @endif +@endforeach +
    +@endif +{{-- + +

    Current piggy banks

    @if($countNonRepeating == 0)

    No piggy banks found.

    @else @foreach($piggybanks as $piggyBank) @if($piggyBank->repeats == 0) -

    {{{$piggyBank->name}}}

    +

    @@ -139,6 +157,20 @@ + + + + + + + +--}}

    Account information

    @@ -157,17 +189,4 @@
    - - - - - - - @stop From 9c69949e8c62288e9f8a8f439bd9b6c6df379f63 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 15:59:10 +0100 Subject: [PATCH 18/57] Added edit and new buttons. --- app/views/piggybanks/index.blade.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index e3cfd2aa0b..23bd539030 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -22,12 +22,27 @@ @endif

    +
    + + +
    + @endif @endforeach +
    +
    +
    +   +
    + +
    +
    @endif {{-- From da7802a0a4deda748a20c347cd0fdad7e56534cc Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 16:15:16 +0100 Subject: [PATCH 19/57] New controller, updated composer. --- app/controllers/GoogleChartController.php | 8 ++++++++ composer.json | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 app/controllers/GoogleChartController.php diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php new file mode 100644 index 0000000000..ce36712823 --- /dev/null +++ b/app/controllers/GoogleChartController.php @@ -0,0 +1,8 @@ + Date: Tue, 28 Oct 2014 16:29:24 +0100 Subject: [PATCH 20/57] Initial set of code required for GCharts. --- public/assets/javascript/firefly/gcharts.js | 26 + .../javascript/firefly/gcharts.options.js | 3 + public/assets/javascript/firefly/index.js | 252 +---- .../javascript/highcharts/highcharts.js | 307 ------ .../stylesheets/highslide/highslide.css | 889 ------------------ 5 files changed, 49 insertions(+), 1428 deletions(-) create mode 100644 public/assets/javascript/firefly/gcharts.js create mode 100644 public/assets/javascript/firefly/gcharts.options.js delete mode 100644 public/assets/javascript/highcharts/highcharts.js delete mode 100755 public/assets/stylesheets/highslide/highslide.css diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js new file mode 100644 index 0000000000..f99b5e0620 --- /dev/null +++ b/public/assets/javascript/firefly/gcharts.js @@ -0,0 +1,26 @@ +google.load('visualization', '1.0', {'packages': ['corechart']}); + +/* + If this method has not been defined (yet) it will error out. + */ +function googleLineChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Create a new google charts object. + */ + var chart = new google.visualization.LineChart(document.getElementById(container)); + + /* + Draw it: + */ + chart.draw(gdata, defaultLineChartOptions); + + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} \ No newline at end of file diff --git a/public/assets/javascript/firefly/gcharts.options.js b/public/assets/javascript/firefly/gcharts.options.js new file mode 100644 index 0000000000..594d1e26ad --- /dev/null +++ b/public/assets/javascript/firefly/gcharts.options.js @@ -0,0 +1,3 @@ +var defaultLineChartOptions = { + +}; \ No newline at end of file diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index b8d5efda98..834dc8db66 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -1,256 +1,44 @@ +/* +This line is required to be properly triggered by Google. + */ +google.setOnLoadCallback(drawChart); + + +function drawChart() { + console.log(1); + googleLineChart('chart/home/account', 'accounts-chart'); +} + + + $(function () { + + + //googleLineChart(); /** * get data from controller for home charts: */ $.getJSON('chart/home/account').success(function (data) { - var options = { - chart: { - renderTo: 'accounts-chart', - type: 'line' - }, - - series: data.series, - title: { - text: null - }, - yAxis: { - allowDecimals: false, - labels: { - formatter: function () { - if (this.value >= 1000 || this.value <= -1000) { - return '\u20AC ' + (this.value / 1000) + 'k'; - } - return '\u20AC ' + this.value; - - } - }, - title: {text: null} - }, - xAxis: { - type: 'datetime', - dateTimeLabelFormats: { - day: '%e %b', - week: '%e %b' - }, - title: { - text: null - } - }, - legend: {enabled: false}, - tooltip: { - formatter: function () { - return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); - } - }, - plotOptions: { - line: { - shadow: true - }, - series: { - cursor: 'pointer', - negativeColor: '#FF0000', - threshold: 0, - lineWidth: 1, - marker: { - radius: 0 - }, - point: { - events: { - click: function (e) { - alert('click!'); - } - } - } - } - }, - credits: { - enabled: false - } - }; - $('#accounts-chart').highcharts(options); + //$('#accounts-chart').highcharts(options); }); /** * Get chart data for categories chart: */ $.getJSON('chart/home/categories').success(function (data) { - $('#categories').highcharts({ - chart: { - type: 'column' - }, - title: { - text: null - }, - credits: { - enabled: false - }, - xAxis: { - type: 'category', - labels: { - rotation: -45, - style: { - fontSize: '12px', - fontFamily: 'Verdana, sans-serif' - } - } - }, - yAxis: { - min: 0, - title: { - text: null - }, - labels: { - formatter: function () { - if (this.value >= 1000 || this.value <= -1000) { - return '\u20AC ' + (this.value / 1000) + 'k'; - } - return '\u20AC ' + this.value; - - } - }, - }, - legend: { - enabled: false - }, - tooltip: { - pointFormat: 'Total expense: \u20AC {point.y:.2f}', - }, - plotOptions: { - column: { - cursor: 'pointer' - } - }, - series: [ - { - name: 'Population', - data: data, - - events: { - click: function (e) { - alert('klik!'); - } - }, - dataLabels: { - enabled: false - } - } - ] - }); + //$('#categories-chart'); }); /** * Get chart data for budget charts. */ $.getJSON('chart/home/budgets').success(function (data) { - $('#budgets').highcharts({ - chart: { - type: 'bar' - }, - title: { - text: null - }, - subtitle: { - text: null - }, - xAxis: { - categories: data.labels, - title: { - text: null - }, - labels: { - style: { - fontSize: '11px', - } - } - }, - tooltip: { - formatter: function () { - return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); - } - }, - yAxis: { - min: 0, - title: {text: null}, + //$('#budgets-chart'); - labels: { - overflow: 'justify', - formatter: function () { - if (this.value >= 1000 || this.value <= -1000) { - return '\u20AC ' + (this.value / 1000) + 'k'; - } - return '\u20AC ' + this.value; - - } - } - }, - plotOptions: { - bar: { - cursor: 'pointer', - events: { - click: function (e) { - if (e.point.url != null) { - window.location = e.point.url; - } - } - }, - dataLabels: { - enabled: true, - formatter: function () { - return '\u20AC ' + Highcharts.numberFormat(this.y, 2); - } - } - } - }, - legend: { - enabled: false - }, - credits: { - enabled: false - }, - series: data.series - }); }); $.getJSON('chart/home/recurring').success(function (data) { - if (data[0].data.length == 0) { - $('#recurring').parent().parent().remove(); - } else { - $('#recurring').highcharts({ - title: { - text: null - }, - credits: { - enabled: false - }, - tooltip: { - formatter: function () { - if (this.point.objType == 'paid') { - return this.key + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); - } else { - return this.key + ': ~\u20AC ' + Highcharts.numberFormat(this.y, 2); - } - - } - }, - plotOptions: { - pie: { - events: { - click: function (e) { - if (e.point.url != null) { - window.location = e.point.url; - } - } - }, - allowPointSelect: true, - cursor: 'pointer', - dataLabels: { - enabled: false - } - } - }, - series: data - }); - } + //$('#recurring-chart'); }); }); \ No newline at end of file diff --git a/public/assets/javascript/highcharts/highcharts.js b/public/assets/javascript/highcharts/highcharts.js deleted file mode 100644 index d37670c432..0000000000 --- a/public/assets/javascript/highcharts/highcharts.js +++ /dev/null @@ -1,307 +0,0 @@ -/* - Highcharts JS v4.0.3 (2014-07-03) - - (c) 2009-2014 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function w(){var a,b=arguments,c,d={},e=function(a,b){var c,d;typeof a!=="object"&&(a={});for(d in b)b.hasOwnProperty(d)&&(c=b[d],a[d]=c&&typeof c==="object"&&Object.prototype.toString.call(c)!=="[object Array]"&&d!=="renderTo"&&typeof c.nodeType!=="number"?e(a[d]||{},c):b[d]);return a};b[0]===!0&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+Q(a-c).toFixed(f).slice(2):"")}function Ha(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Ma(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);a.unshift(d); -return c.apply(this,a)}}function Ia(a,b){for(var c="{",d=!1,e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h-1?h.thousandsSep:""))):e=bb(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function lb(a){return V.pow(10,U(V.log(a)/V.LN10))} -function mb(a,b,c,d){var e,c=p(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;dc&&(c=a[b]); -return c}function Oa(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Pa(a){cb||(cb=$(Ja));a&&cb.appendChild(a);cb.innerHTML=""}function ea(a){return parseFloat(a.toPrecision(14))}function Qa(a,b){va=p(a,b.animation)}function Ab(){var a=L.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";Ra=(a&&L.global.timezoneOffset||0)*6E4;db=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,p(c,1),p(g,0),p(h,0),p(i,0))).getTime()};ob=b+"Minutes";pb=b+"Hours";qb=b+"Day"; -Wa=b+"Date";eb=b+"Month";fb=b+"FullYear";Bb=c+"Minutes";Cb=c+"Hours";rb=c+"Date";Db=c+"Month";Eb=c+"FullYear"}function G(){}function Sa(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function ma(){this.init.apply(this,arguments)}function Xa(){this.init.apply(this,arguments)}function Fb(a,b,c,d,e){var f=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.alignOptions={align:b.align||(f?c?"left": -"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:p(b.y,f?4:c?14:-6),x:p(b.x,f?c?-6:6:0)};this.textAlign=b.textAlign||(f?c?"right":"left":"center")}var t,x=document,H=window,V=Math,v=V.round,U=V.floor,Ka=V.ceil,u=V.max,C=V.min,Q=V.abs,aa=V.cos,fa=V.sin,na=V.PI,Ca=na*2/360,wa=navigator.userAgent,Gb=H.opera,Aa=/msie/i.test(wa)&&!Gb,gb=x.documentMode===8,sb=/AppleWebKit/.test(wa),Ta=/Firefox/.test(wa),Hb=/(Mobile|Android|Windows Phone)/.test(wa),xa="http://www.w3.org/2000/svg", -ba=!!x.createElementNS&&!!x.createElementNS(xa,"svg").createSVGRect,Nb=Ta&&parseInt(wa.split("Firefox/")[1],10)<4,ga=!ba&&!Aa&&!!x.createElement("canvas").getContext,Ya,Za,Ib={},tb=0,cb,L,bb,va,ub,B,oa,sa=function(){return t},W=[],$a=0,Ja="div",P="none",Ob=/^[0-9]+$/,Pb="stroke-width",db,Ra,ob,pb,qb,Wa,eb,fb,Bb,Cb,rb,Db,Eb,J={},S;H.Highcharts?oa(16,!0):S=H.Highcharts={};bb=function(a,b,c){if(!s(b)||isNaN(b))return"Invalid date";var a=p(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b-Ra),e,f=d[pb](),g=d[qb](), -h=d[Wa](),i=d[eb](),j=d[fb](),k=L.lang,l=k.weekdays,d=r({a:l[g].substr(0,3),A:l[g],d:Ha(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ha(i+1),y:j.toString().substr(2,2),Y:j,H:Ha(f),I:Ha(f%12||12),l:f%12||12,M:Ha(d[ob]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Ha(d.getSeconds()),L:Ha(v(b%1E3),3)},S.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]==="function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};oa=function(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+ -a;if(b)throw c;H.console&&console.log(c)};B={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5,month:26784E5,year:31556952E3};ub={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c); -a.shift=0;if(b.length)for(a=c.length;b.length{point.key}
    ',pointFormat:' {series.name}: {point.y}
    ',shadow:!0,snap:Hb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer", -color:"#909090",fontSize:"9px"}}};var ca=L.plotOptions,T=ca.line;Ab();var Tb=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Ub=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Vb=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,ya=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Ua(a.stops,function(a){return ya(a[1])}):(c=Tb.exec(a))?b=[z(c[1]),z(c[2]),z(c[3]),parseFloat(c[4],10)]:(c=Ub.exec(a))?b=[z(c[1],16),z(c[2],16),z(c[3], -16),1]:(c=Vb.exec(a))&&(b=[z(c[1]),z(c[2]),z(c[3]),1])})(a);return{get:function(c){var f;d?(f=w(a),f.stops=[].concat(f.stops),q(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)q(d,function(b){b.brighten(a)});else if(ia(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=z(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}}; -G.prototype={opacity:1,textProps:"fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow,HcTextStroke".split(","),init:function(a,b){this.element=b==="span"?$(b):x.createElementNS(xa,b);this.renderer=a},animate:function(a,b,c){b=p(b,va,!0);ab(this);if(b){b=w(b,{});if(c)b.complete=c;ib(this,a,b)}else this.attr(a),c&&c();return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,m,n,o=[];a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient"); -if(f){g=a[f];h=d.gradients;j=a.stops;m=c.radialReference;La(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&m&&!s(g.gradientUnits)&&(g=w(g,{cx:m[0]-m[2]/2+g.cx*m[2],cy:m[1]-m[2]/2+g.cy*m[2],r:g.r*m[2],gradientUnits:"userSpaceOnUse"}));for(n in g)n!=="id"&&o.push(n,g[n]);for(n in j)o.push(j[n]);o=o.join(",");h[o]?a=h[o].attr("id"):(g.id=a="highcharts-"+tb++,h[o]=i=d.createElement(f).attr(g).add(d.defs),i.stops=[],q(j,function(a){a[1].indexOf("rgba")=== -0?(e=ya(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;typeof a==="string"&&b!==t&&(c=a,a={},a[c]=b);if(typeof a==="string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a){d=a[c];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(f||(this.symbolAttr(a), -f=!0),h=!0);if(this.rotation&&(c==="x"||c==="y"))this.doTransform=!0;h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?u(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element,c=F(b,"class")|| -"";c.indexOf(a)===-1&&F(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;q("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=p(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":P)},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||0;d=v(e)%2/2;a.x=U(a.x||this.x||0)+d;a.y=U(a.y||this.y||0)+d;a.width=U((a.width||this.width|| -0)-2*d);a.height=U((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()==="text"&&z(a.width);b&&(a=r(b,c));this.styles=a;e&&(ga||!ba&&this.renderer.forExport)&&delete a.width;if(Aa&&!ba)A(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()}; -for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";F(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;Za&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(wa.indexOf("Android")===-1||Date.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a, -translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(s(c)||s(d))&&a.push("scale("+p(c,1)+" "+p(d,1)+")");a.length&&g.setAttribute("transform", -a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Fa(c))this.alignTo=d=c||"renderer",ka(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=p(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d]; -h[b?"translateX":"x"]=v(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=v(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*Ca;d=this.textStr;var h;if(d===""||Ob.test(d))h="num."+d.toString().length+(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if(c.namespaceURI===xa||b.forExport){try{a= -c.getBBox?r({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(i){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){c=a.width;d=a.height;if(Aa&&f&&f.fontSize==="11px"&&d.toPrecision(3)==="16.9")a.height=d=14;if(e)a.width=Q(d*fa(g))+Q(c*aa(g)),a.height=Q(d*aa(g))+Q(c*fa(g))}this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){return a&&this.element.namespaceURI===xa?(this.element.removeAttribute("visibility"),this):this.attr({visibility:a?"inherit":"visible"})}, -hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(f)c.handleZ=!0,f=z(f);if(c.handleZ){a=d.childNodes;for(g=0;gf||!s(f)&&s(c))){d.insertBefore(e, -b);h=!0;break}}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;ab(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f/,i=/<.*href="(http[^"]+)".*>/,l&&!a.added&&this.box.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(//g,"").split(//g):[e],e[e.length-1]===""&&e.pop(),q(e,function(e,f){var g,m=0,e=e.replace(//g,"|||");g=e.split("|||");q(g,function(e){if(e!== -""||g.length===1){var n={},o=x.createElementNS(xa,"tspan"),p;h.test(e)&&(p=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),F(o,"style",p));i.test(e)&&!d&&(F(o,"onclick",'location.href="'+e.match(i)[1]+'"'),A(o,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(e!==" "){o.appendChild(x.createTextNode(e));if(m)n.dx=0;else if(f&&j!==null)n.x=j;F(o,n);b.appendChild(o);!m&&f&&(!ba&&d&&A(o,{display:"block"}),F(o,"dy",Y(o)));if(l)for(var e=e.replace(/([^\^])-/g, -"$1- ").split(" "),n=g.length>1||e.length>1&&k.whiteSpace!=="nowrap",q,E,s=k.HcHeight,u=[],t=Y(o),Kb=1;n&&(e.length||u.length);)delete a.bBox,q=a.getBBox(),E=q.width,!ba&&c.forExport&&(E=c.measureSpanWidth(o.firstChild.data,a.styles)),q=E>l,!q||e.length===1?(e=u,u=[],e.length&&(Kb++,s&&Kb*t>s?(e=["..."],a.attr("title",a.textStr)):(o=x.createElementNS(xa,"tspan"),F(o,{dy:t,x:j}),p&&F(o,"style",p),b.appendChild(o))),E>l&&(l=E)):(o.removeChild(o.firstChild),u.unshift(e.pop())),e.length&&o.appendChild(x.createTextNode(e.join(" ").replace(/- /g, -"-")));m++}}})}))},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a,b,c,i,null,null,null,null,"button"),k=0,l,m,n,o,p,q,a={x1:0,y1:0,x2:0,y2:1},e=w({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);o=f.style;delete f.style;g=w(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);p=g.style; -delete g.style;h=w(e,{style:{color:"#CCC"}},h);q=h.style;delete h.style;N(j.element,Aa?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(o)});N(j.element,Aa?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],m=[n,o,p][k],j.attr(l).css(m))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(p):a===3&&j.attr(h).css(q):j.attr(e).css(n)};return j.on("click",function(){k!==3&&d.call(j)}).attr(e).css(r({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=v(a[1])-b% -2/2);a[2]===a[5]&&(a[2]=a[5]=v(a[2])+b%2/2);return a},path:function(a){var b={fill:P};La(a)?b.d=a:da(a)&&r(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=da(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(da(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e|| -0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){var e=da(a)?a.r:e,g=this.createElement("rect"),a=da(a)?a:a===t?{}:{x:a,y:b,width:u(c,0),height:u(d,0)};if(f!==t)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){F(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[p(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return s(a)? -b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:P};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(v(b),v(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f);else if(i.test(a))k= -function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(v((d-b[0])/2),v((e-b[1])/2)))},j=a.match(i)[1],a=Ib[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),$("img",{onload:function(){k(g,Ib[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+ -d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=aa(f),j=fa(f),k=aa(g),g=fa(g),e=e.end-fc&&i>b+g&&ib+g&&id&&h>a+g&&ha+g&&hl&&/[ \-]/.test(b.textContent||b.innerText))A(b,{width:l+"px",display:"block",whiteSpace:"normal"}),i=l;this.getSpanCorrection(i,k,h,j,g)}A(b,{left:e+ -(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(sb)k=b.offsetHeight;this.cTT=m}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=Aa?"-ms-transform":sb?"-webkit-transform":Ta?"MozTransform":Gb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Ta?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";A(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});r(ta.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element, -f=d.renderer;d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a};d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()};d.attr({text:a,x:v(b),y:v(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});d.css=d.htmlCss;if(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,j=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)j.push(a),a=a.parentGroup; -q(j.reverse(),function(a){var d;b=a.div=a.div||$(Ja,{className:F(a.element,"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;r(a,{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0},visibilitySetter:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var Z;if(!ba&&!ga){Z={init:function(a, -b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ja;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=$(c);this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&& -this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:G.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=aa(a*Ca),c=fa(a*Ca);A(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):P})},getSpanCorrection:function(a,b,c,d,e){var f=d?aa(d*Ca):1,g=d?fa(d*Ca):0,h=p(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g< -0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),A(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(ia(a[b]))c[b]=v(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,ka(c,b),c.push(b), -b.destroyClip=function(){ka(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:gb?"inherit":"rect(auto)"});return b.css(a)},css:G.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Pa(a)},destroy:function(){this.destroyClip&&this.destroyClip();return G.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=H.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=z(a[c- -2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,n,o;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){n=p(a.width,3);o=(a.opacity||0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=[''];h=$(g.prepVML(j),null,{left:z(i.left)+p(a.offsetX,1),top:z(i.top)+p(a.offsetY,1)});if(c)h.cutOff= -l+1;j=[''];$(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:sa,setAttr:function(a,b){gb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]||$(this.renderer.prepVML([""]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b, -c){var d=this.shadows,a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff?this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!==P,this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},opacitySetter:sa,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-v(fa(a*Ca)+1)+"px";c.top=v(aa(a*Ca))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor", -this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;ia(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&q(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,gb||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]= -a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}};S.VMLElement=Z=la(G,Z);Z.prototype.ySetter=Z.prototype.widthSetter=Z.prototype.heightSetter=Z.prototype.xSetter;var ha={Element:Z,isIE8:wa.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ja).css(r(this.getStyle(d),{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.cache={};this.setSize(b,c,!1);if(!x.namespaces.hcv){x.namespaces.add("hcv", -"urn:schemas-microsoft-com:vml");try{x.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){x.styleSheets[0].cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=da(a);return r(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width: -c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+v(a?e:d)+"px,"+v(a?f:b)+"px,"+v(a?b:f)+"px,"+v(a?d:e)+"px)"};!a&&gb&&c==="DIV"&&r(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){q(e.members,function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=P;a&&a.linearGradient?i="gradient":a&&a.radialGradient&& -(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,n,o,p,E,I,D="",a=a.stops,u,s=[],t=function(){h=[''];$(e.prepVML(h),null,null,b)};n=a[0];u=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);u[0]<1&&a.push([1,u[1]]);q(a,function(a,b){g.test(a[1])?(f=ya(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);s.push(a[0]*100+"% "+k);b?(p=l,E=k):(o=l,I=k)});if(c==="fill")if(i==="gradient")c= -m.x1||m[0]||0,a=m.y1||m[1]||0,n=m.x2||m[2]||0,m=m.y2||m[3]||0,D='angle="'+(90-V.atan((m-a)/(n-c))*180/na)+'"',t();else{var j=m.r,r=j*2,v=j*2,x=m.cx,y=m.cy,R=b.radialReference,w,j=function(){R&&(w=d.getBBox(),x+=(R[0]-w.x)/w.width-0.5,y+=(R[1]-w.y)/w.height-0.5,r*=R[2]/w.width,v*=R[2]/w.height);D='src="'+L.global.VMLRadialGradientURL+'" size="'+r+","+v+'" origin="0.5,0.5" position="'+x+","+y+'" color2="'+I+'" ';t()};d.added?j():d.onAdd=j;j=E}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=ya(a),h= -["<",c,' opacity="',f.get("a"),'"/>'],$(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.attr({x:b,y:c,width:d, -height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):ta.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;A(a,{flip:"x",left:z(d.width)-(e?z(e.top):1),top:z(d.height)-(e?z(e.left):1),rotation:-90});q(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=aa(f),i=fa(f),j=aa(g),k=fa(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d, -b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){return ta.prototype.symbols[!s(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}};S.VMLRenderer=Z=function(){this.init.apply(this,arguments)};Z.prototype=w(ta.prototype,ha);Ya=Z}ta.prototype.measureSpanWidth=function(a,b){var c= -x.createElement("span"),d;d=x.createTextNode(a);c.appendChild(d);A(c,b);this.box.appendChild(c);d=c.offsetWidth;Pa(c);return d};var Lb;if(ga)S.CanVGRenderer=Z=function(){xa="http://www.w3.org/1999/xhtml"},Z.prototype.symbols={},Lb=function(){function a(){var a=b.length,d;for(d=0;dl[o]?l[o]=g+j:m||(c=!1);if(m){l=(m=d.justifyToPlot)?d.pos:0;m=m?l+d.len: -d.chart.chartWidth;do a+=e?1:-1,n=d.ticks[i[a]];while(i[a]&&(!n||!n.label||n.label.line!==o));d=n&&n.label.xy&&n.label.xy.x+n.getLabelSides()[e?0:1];e&&!h||f&&h?g+kd&&(c=!1)):g+j>m&&(g=m-j,n&&g+k0&&b.height>0){f=w({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g){r={align:f.textAlign||f.align,rotation:f.rotation};if(s(I))r.zIndex=I;a.label=g=t.text(f.text,0,0,f.useHTML).attr(r).css(f.style).add()}b=[o[1],o[4],k?o[6]:o[1]];k=[o[2],o[5],k?o[7]:o[2]];o=Na(b);c=Na(k);g.align(f,!1,{x:o,y:c,width:Ba(b)-o,height:Ba(k)-c});g.show()}else g&&g.hide();return a},destroy:function(){ka(this.axis.plotLinesAndBands, -this);delete this.axis;Oa(this)}};ma.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:M,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10, -tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Ga(this.total,-1)},style:M.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}}, -defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter; -this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=s(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement==="between"?0.5:0;this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom; -this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=p(d.crosshair,ra(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Da(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===t)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)N(this,f,d[f]);if(this.isLog)this.val2lin= -za,this.lin2val=ja},setOptions:function(a){this.options=w(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],w(L[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=L.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ia(h,this);else if(c)g=b;else if(d)g= -bb(d,b);else if(f&&a>=1E3)for(;f--&&g===t;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Ga(b/c,-1)+e[f]);g===t&&(g=Q(b)>=1E4?Ga(b,0):Ga(b,-1,t,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.buildStacks&&a.buildStacks();q(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=C(p(a.dataMin,d[0]), -Na(d)),a.dataMax=u(p(a.dataMax,d[0]),Ba(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(s(c)&&s(e))a.dataMin=C(p(a.dataMin,c),c),a.dataMax=u(p(a.dataMax,e),e);if(s(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMaxg+this.width)m=!0}else if(a=g,c=l-this.right,ih+this.height)m=!0;return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ea(U(b/a)*a),f=ea(Ka(c/a)*a),g=[];if(b===c&&ia(b))return[b];for(b=e;b<=f;){g.push(b);b=ea(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a= -this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===t&&!this.isLog)s(a.min)||s(a.max)?this.minRange=null:(q(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===t||hc&&(h=0);d=u(d,h);f=u(f,Fa(j)?0:h/2);g=u(g,j==="on"?0:h);!a.noSharedTooltip&&s(n)&&(e=s(e)?C(e,n):n)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding= -g*=h,b.pointRange=C(d,c),b.closestPointRange=e;if(a)b.oldTransA=j;b.translationSlope=b.transA=j=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=j*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=d.startOnTick,f=d.endOnTick,g=b.isLog,h=b.isDatetimeAxis,i=b.isXAxis,j=b.isLinked,k=b.options.tickPositioner,l=d.maxPadding,m=d.minPadding,n=d.tickInterval,o=d.minTickInterval,Y=d.tickPixelInterval,E,I=b.categories;j?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(), -b.min=p(c.min,c.dataMin),b.max=p(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&oa(11,1)):(b.min=p(b.userMin,d.min,b.dataMin),b.max=p(b.userMax,d.max,b.dataMax));if(g)!a&&C(b.min,p(b.dataMin,b.min))<=0&&oa(10,1),b.min=ea(za(b.min)),b.max=ea(za(b.max));if(b.range&&s(b.max))b.userMin=b.min=u(b.min,b.max-b.range),b.userMax=b.max,b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!I&&!b.axisPointRange&&!b.usePercentage&&!j&&s(b.min)&&s(b.max)&&(c=b.max-b.min)){if(!s(d.min)&& -!s(b.userMin)&&m&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*m;if(!s(d.max)&&!s(b.userMax)&&l&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*l}if(ia(d.floor))b.min=u(b.min,d.floor);if(ia(d.ceiling))b.max=C(b.max,d.ceiling);b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:j&&!n&&Y===b.linkedParent.options.tickPixelInterval?b.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=p(n,I?1:(b.max-b.min)*Y/u(b.len,Y)),!s(n)&&b.lenu(2*b.len,200)&&oa(19,!0),a=h?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):g?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),E&&a.splice(1,a.length-2),b.tickPositions=a;if(!j)d=a[0],g=a[a.length- -1],h=b.minPointOffset||0,!e&&!f&&!I&&a.length===2&&a.splice(1,0,(g+d)/2),e?b.min=d:b.min-h>d&&a.shift(),f?b.max=g:b.max+h1E13?1:0.001,b.min-=e,b.max+=e)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.coll,this.pos,this.len].join("-");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey, -b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1&&this.min!==t){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e=u(d,p(e.max,d))&&(b=t));this.displayBtn=a!==t||b!==t;this.setExtremes(a,b,!1,t, -{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=p(b.width,a.plotWidth-c+(b.offsetRight||0)),f=p(b.height,a.plotHeight),g=p(b.top,a.plotTop),b=p(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseInt(f,10)/100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop);this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=u(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a= -this.isLog;return{min:a?ea(ja(this.min)):this.min,max:a?ea(ja(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ja(this.min):this.min,b=b?ja(this.max):this.max;c>a||a===null?a=c:b15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options, -e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k,l=0,m,n=0,o=d.title,Y=d.labels,E=0,I=b.axisOffset,b=b.clipOffset,D=[-1,1,1,-1][h],r,v=1,w=p(Y.maxStaggerLines,5),x,z,C,y,R;a.hasData=j=a.hasVisibleSeries||s(a.min)&&s(a.max)&&!!e;a.showAxis=k=j||p(d.showEmpty,!0);a.staggerLines=a.horiz&&Y.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:Y.zIndex|| -7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add();if(j||a.isLinked){a.labelAlign=p(Y.align||a.autoLabelAlign(Y.rotation));q(e,function(b){f[b]?f[b].addLabel():f[b]=new Sa(a,b)});if(a.horiz&&!a.staggerLines&&w&&!Y.rotation){for(j=a.reversed?[].concat(e).reverse():e;v1)a.staggerLines=v}q(e,function(b){if(h=== -0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)E=u(f[b].getLabelSize(),E)});if(a.staggerLines)E*=a.staggerLines,a.labelOffset=E}else for(r in f)f[r].destroy(),delete f[r];if(o&&o.text&&o.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(o.text,0,0,o.useHTML).attr({zIndex:7,rotation:o.rotation||0,align:o.textAlign||{low:"left",middle:"center",high:"right"}[o.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(o.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(k)l=a.axisTitle.getBBox()[g? -"height":"width"],m=o.offset,n=s(m)?0:p(o.margin,g?5:10);a.axisTitle[k?"show":"hide"]()}a.offset=D*p(d.offset,I[h]);c=h===2?a.tickBaseline:0;g=E+n+(E&&D*(g?p(Y.y,a.tickBaseline+8):Y.x)-c);a.axisTitleMargin=p(m,g);I[h]=u(I[h],a.axisTitleMargin+l+D*a.offset,g);b[i]=u(b[i],U(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e? -this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=z(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this, -b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,i=a.tickPositions,j,k=a.axisTitle,l=a.ticks,m=a.minorTicks,n=a.alternateBands,o=f.stackLabels,p=f.alternateGridColor,E=a.tickmarkOffset,I=f.lineWidth,D=d.hasRendered&&s(a.oldMin)&&!isNaN(a.oldMin),r=a.hasData,u=a.showAxis,v,w=f.labels.overflow,x=a.justifyLabels=b&&w!==!1,z;a.labelEdge.length=0;a.justifyToPlot=w==="justify";q([l,m,n],function(a){for(var b in a)a[b].isActive=!1});if(r||h)if(a.minorTickInterval&&!a.categories&& -q(a.getMinorTickPositions(),function(b){m[b]||(m[b]=new Sa(a,b,"minor"));D&&m[b].isNew&&m[b].render(null,!0);m[b].render(null,!1,1)}),i.length&&(j=i.slice(),(b&&c||!b&&!c)&&j.reverse(),x&&(j=j.slice(1).concat([j[0]])),q(j,function(b,c){x&&(c=c===j.length-1?0:c+1);if(!h||b>=a.min&&b<=a.max)l[b]||(l[b]=new Sa(a,b)),D&&l[b].isNew&&l[b].render(c,!0,0.1),l[b].render(c)}),E&&a.min===0&&(l[-1]||(l[-1]=new Sa(a,-1,null,!0)),l[-1].render(-1))),p&&q(i,function(b,c){if(c%2===0&&b=B.second&&(i.setMilliseconds(0),i.setSeconds(j>=B.minute?0:k*U(i.getSeconds()/k)));if(j>=B.minute)i[Bb](j>=B.hour?0:k*U(i[ob]()/k));if(j>=B.hour)i[Cb](j>= -B.day?0:k*U(i[pb]()/k));if(j>=B.day)i[rb](j>=B.month?1:k*U(i[Wa]()/k));j>=B.month&&(i[Db](j>=B.year?0:k*U(i[eb]()/k)),h=i[fb]());j>=B.year&&(h-=h%k,i[Eb](h));if(j===B.week)i[rb](i[Wa]()-i[qb]()+p(d,1));b=1;Ra&&(i=new Date(i.getTime()+Ra));h=i[fb]();for(var d=i.getTime(),l=i[eb](),m=i[Wa](),n=g?Ra:(864E5+i.getTimezoneOffset()*6E4)%864E5;d=0.5)a=v(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=U(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||k<=c)&&k!==t&&g.push(k),k>c&&(l=!0),k=j}else if(b=ja(b), -c=ja(c),a=e[d?"minorTickInterval":"tickInterval"],a=p(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=mb(a,null,lb(a)),g=Ua(this.getLinearTickPositions(a,b,c),za),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g};var Mb=S.Tooltip=function(){this.init.apply(this,arguments)};Mb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=z(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0, -y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});ga||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden&& -(Q(a-f.x)>1||Q(b-f.y)>1),h=e.followPointer||e.len>1;r(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?t:g?(2*f.anchorX+c)/3:c,anchorY:h?t:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g)clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a=this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},p(this.options.hideDelay,500)),b&&q(b,function(a){a.setState()}), -this.chart.hoverPoints=null},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=ra(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===t&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(q(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return Ua(c,v)},getPosition:function(a,b,c){var d= -this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],i=["x",d.chartWidth,a,c.plotX+d.plotLeft],j=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,k=function(a,b,c,d){var g=cb-e)return!1;else f[a]=db-c/2?b-c-2:d-c/2},m=function(a){var b=h;h=i;i=b;g=a},n=function(){k.apply(0,h)!==!1?l.apply(0,i)===!1&&!g&&(m(!0),n()): -g?f.x=f.y=0:(m(!0),n())};(d.inverted||this.len>1)&&m();n();return f},defaultFormatter:function(a){var b=this.points||ra(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];q(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints, -k,l=this.shared;clearTimeout(this.hideTimer);this.followPointer=ra(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];l&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&q(h,function(a){a.setState()}),q(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=j,this.len=j.length,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;this.distance=p(h.tooltipOptions.distance,16);i===!1?this.hide():(this.isHidden&&(ab(d), -d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);K(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(v(c.x),v(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b= -a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&f.options.type==="datetime"&&ia(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in B){if(B[h]>=f||B[h]<=B.day&&a.key%B[h]>0){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+e+"}"));return Ia(c,{point:a,series:b})}};var pa;Za=x.documentElement.ontouchstart!==t;var Va=S.Pointer=function(a,b){this.init(a,b)};Va.prototype={init:function(a,b){var c=b.chart,d= -c.events,e=ga?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(S.Tooltip&&b.tooltip.enabled)a.tooltip=new Mb(a,b.tooltip),this.followTouchMove=b.tooltip.followTouchMove;this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=Sb(a);if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length? -a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=Rb(this.chart.container);d.pageX===t?(c=u(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return r(a,{chartX:v(c),chartY:v(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};q(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft}, -runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,h=b.hoverSeries,i,j,k=b.chartWidth,l=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];i=c.length;for(j=0;jk&&f.splice(i,1);if(f.length&& -f[0].clientX!==this.hoverX)d.refresh(f,a),this.hoverX=f[0].clientX}c=h&&h.tooltipOptions.followPointer;if(h&&h.tracker&&!c){if((e=h.tooltipPoints[l])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));if(d&&!this._onDocumentMouseMove)this._onDocumentMouseMove=function(a){if(W[pa])W[pa].pointer.onDocumentMouseMove(a)},N(x,"mousemove",this._onDocumentMouseMove);q(b.axes,function(b){b.drawCrosshair(a,p(e,g))})},reset:function(a){var b=this.chart, -c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,f=e&&e.shared?b.hoverPoints:d;(a=a&&e&&f)&&ra(f)[0].plotX===t&&(a=!1);if(a)e.refresh(f),d&&d.setState(d.state,!0);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&e.hide();if(this._onDocumentMouseMove)X(x,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=null;q(b.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;q(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d), -e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,n=this.mouseDownY, -o=c.panKey&&a[c.panKey+"Key"];dh+j&&(d=h+j);ei+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(m-d,2)+Math.pow(n-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,n-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!o&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:Q(d),x:(d>0?0:d)+m}));this.selectionMarker&& -g&&(d=e-n,this.selectionMarker.attr({height:Q(d),y:(d>0?0:d)+n}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.attr?e.attr("x"):e.x,g=e.attr?e.attr("y"):e.y,h=e.attr?e.attr("width"):e.width,i=e.attr?e.attr("height"):e.height,j;if(this.hasDragged||c)q(b.axes,function(b){if(b.zoomEnabled){var c=b.horiz,e=a.type==="touchend"?b.minPixelPadding: -0,n=b.toValue((c?f:g)+e),c=b.toValue((c?f+h:g+i)-e);!isNaN(n)&&!isNaN(c)&&(d[b.coll].push({axis:b,min:C(n,c),max:u(n,c)}),j=!0)}}),j&&K(b,"selection",d,function(a){b.zoom(r(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)A(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault(); -this.dragStart(a)},onDocumentMouseUp:function(a){W[pa]&&W[pa].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){var a=W[pa];if(a)a.pointer.reset(),a.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;pa=b.index;a=this.normalize(a);a.returnValue= -!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=F(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget||a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a, -"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(K(c.series,"click",r(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(r(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&K(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)}; -b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};N(b,"mouseleave",a.onContainerMouseLeave);$a===1&&N(x,"mouseup",a.onDocumentMouseUp);if(Za)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},$a===1&&N(x,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a;X(this.chart.container,"mouseleave",this.onContainerMouseLeave);$a||(X(x,"mouseup",this.onDocumentMouseUp),X(x,"touchend",this.onDocumentTouchEnd)); -clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};r(S.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i=this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,m=a?"width":"height",n=i["plot"+(a?"Left":"Top")],o,p,q=h||1,r=i.inverted,D=i.bounds[a?"h":"v"],u=b.length===1,s=b[0][l], -v=c[0][l],t=!u&&b[1][l],w=!u&&c[1][l],x,c=function(){!u&&Q(s-t)>20&&(q=h||Q(v-w)/Q(s-t));p=(n-v)/q+s;o=i["plot"+(a?"Width":"Height")]/q};c();b=p;bD.max&&(b=D.max-o,x=!0);x?(v-=0.8*(v-g[j][0]),u||(w-=0.8*(w-g[j][1])),c()):g[j]=[v,w];r||(f[j]=p-n,f[m]=o);f=r?1/q:q;e[m]=o;e[j]=b;d[r?a?"scaleY":"scaleX":"scale"+k]=q;d["translate"+k]=f*n+(v-f*s)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=b.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.hasZoom,j=b.selectionMarker, -k={},l=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||c.runChartClick),m={};(i||e)&&!l&&a.preventDefault();Ua(f,function(a){return b.normalize(a)});if(a.type==="touchstart")q(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],q(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(p(a.options.min,a.dataMin)),f=a.toPixels(p(a.options.max,a.dataMax)),g= -C(e,f),e=u(e,f);b.min=C(a.pos,g-d);b.max=u(a.pos+a.len,e+d)}});else if(d.length){if(!j)b.selectionMarker=j=r({destroy:sa},c.plotBox);b.pinchTranslate(d,f,k,j,m,h);b.hasPinched=i;b.scaleGroups(k,m);!i&&e&&g===1&&this.runPointActions(b.normalize(a))}},onContainerTouchStart:function(a){var b=this.chart;pa=b.index;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length=== -1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){W[pa]&&W[pa].pointer.drop(a)}});if(H.PointerEvent||H.MSPointerEvent){var ua={},yb=!!H.PointerEvent,Wb=function(){var a,b=[];b.item=function(a){return this[a]};for(a in ua)ua.hasOwnProperty(a)&&b.push({pageX:ua[a].pageX,pageY:ua[a].pageY,target:ua[a].target});return b},zb=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&W[pa])d(a),d=W[pa].pointer,d[b]({type:c,target:a.currentTarget, -preventDefault:sa,touches:Wb()})};r(Va.prototype,{onContainerPointerDown:function(a){zb(a,"onContainerTouchStart","touchstart",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){zb(a,"onContainerTouchMove","touchmove",function(a){ua[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!ua[a.pointerId].target)ua[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){zb(a,"onContainerTouchEnd","touchend",function(a){delete ua[a.pointerId]})}, -batchMSEvents:function(a){a(this.chart.container,yb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,yb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(x,yb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});Ma(Va.prototype,"init",function(a,b,c){a.call(this,b,c);(this.hasZoom||this.followTouchMove)&&A(b.container,{"-ms-touch-action":P,"touch-action":P})});Ma(Va.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&& -this.batchMSEvents(N)});Ma(Va.prototype,"destroy",function(a){this.batchMSEvents(X);a.call(this)})}var kb=S.Legend=function(a,b){this.init(a,b)};kb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=p(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.itemStyle=d,c.itemHiddenStyle=w(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=p(b.symbolWidth,16),c.pages=[],c.render(), -N(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g),g)d=g[j],d!==t&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl, -d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;q(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Pa(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c= -b.translateY,q(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,A(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g(m||b.chartWidth-2*j-q-d.x))this.itemX=q,this.itemY+=o+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=u(this.maxItemWidth,f);this.lastItemY=o+this.itemY+n;this.lastLineHeight=u(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=o+g+n,this.lastLineHeight=g);this.offsetWidth=m||u((e?this.itemX-q-k:f)+j,this.offsetWidth)},getAllItems:function(){var a=[];q(this.chart.series, -function(b){var c=b.options;if(p(c.showInLegend,!s(c.linkedTo)?t:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle(); -e=a.getAllItems();nb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;q(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l|| -0,fill:m||P}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;q(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=p(j.animation,!0),l=j.arrowSize||12,m=this.nav,n=this.pages,o,r=this.allItems; -e.layout==="horizontal"&&(f/=2);g&&(f=C(f,g));n.length=0;if(a>f&&!e.useHTML){this.clipHeight=h=u(f-20-this.titleHeight-this.padding,0);this.currentPage=p(this.currentPage,1);this.fullHeight=a;q(r,function(a,b){var c=a._legendItemPos[1],d=v(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(o||c)!==n[e-1])n.push(o||c),e++;b===r.length-1&&c+d-n[e-1]>h&&n.push(c);c!==o&&(o=c)});if(!i)i=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(i);i.attr({height:h});if(!m)this.nav=m= -d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(m),this.pager=d.text("",15,10).css(j.style).add(m),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(m);b.scroll(0);a=f}else if(m)i.attr({height:c.chartHeight}),m.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation, -h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==t&&Qa(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c)}}; -M=S.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-v(e.fontMetrics(a.options.itemStyle.fontSize,this.legendItem).b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle= -b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(wa)||Ta)&&Ma(kb.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Xa.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=w(L,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing= -this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=W.length;W.push(f);$a++;d.reflow!==!1&&N(f,"load",function(){f.initReflow()});if(e)for(g in e)N(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ga?!1:p(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=J[a.type||b.type||b.defaultSeriesType])|| -oa(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&q(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.hasCartesianSeries,j=this.isDirtyBox,k=c.length,l=k,m=this.renderer,n=m.isHidden(),o=[];Qa(a,this);n&&this.cloneRenderTo(); -for(this.layOutTitles();l--;)if(a=c[l],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(l=k;l--;)if(a=c[l],a.options.stacking)a.isDirty=!0;q(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(i){if(!this.isResizing)this.maxTicks=null,q(b,function(a){a.setScale()});this.adjustTickAmounts()}this.getMargins();i&&(q(b,function(a){a.isDirty&&(j=!0)}),q(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes= -!1,o.push(function(){K(a,"afterSetExtremes",r(a.eventArgs,a.getExtremes()));delete a.eventArgs});(j||g)&&a.redraw()}));j&&this.drawChartBox();q(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);m.draw();K(this,"redraw");n&&this.cloneRenderTo(!0);q(o,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d -19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Pa(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),A(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),x.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart, -c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+tb++;if(Fa(a))this.renderTo=a=x.getElementById(a);a||oa(13,!0);c=z(F(a,"data-highcharts-chart"));!isNaN(c)&&W[c]&&W[c].hasRendered&&W[c].destroy();F(a,"data-highcharts-chart",this.index);a.innerHTML="";!b.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=$(Ja,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},r({position:"relative",overflow:"hidden",width:c+ -"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new ta(a,c,d,b.style,!0):new Ya(a,c,d,b.style);ga&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=p(e.margin,20),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!s(d[0]))this.plotTop= -u(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!s(d[1]))this.marginRight=u(this.marginRight,c.legendWidth-g+f+a[1])}else if(i==="left"){if(!s(d[3]))this.plotLeft=u(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!s(d[0]))this.plotTop=u(this.plotTop,c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!s(d[2]))this.marginBottom=u(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&& -(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&q(this.axes,function(a){a.getOffset()});s(d[3])||(this.plotLeft+=b[3]);s(d[0])||(this.plotTop+=b[0]);s(d[2])||(this.marginBottom+=b[2]);s(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||hb(d,"width"),f=c.height||hb(d,"height"),c=a?a.target:H,d=function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null};if(!b.hasUserSize&&e&&f&&(c===H||c===x)){if(e!==b.containerWidth|| -f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};N(H,"resize",b);N(a,"destroy",function(){X(H,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&K(d,"endResize",null,function(){d.isResizing-=1})};Qa(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(s(a))d.chartWidth=e=u(0,v(a)),d.hasUserSize=!!e;if(s(b))d.chartHeight= -f=u(0,v(b));(va?ib:A)(d.container,{width:e+"px",height:f+"px"},va);d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;q(d.axes,function(a){a.isDirty=!0;a.setScale()});q(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;K(d,"resize");va===!1?g():setTimeout(g,va&&va.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing, -h=this.clipOffset,i,j,k,l;this.plotLeft=i=v(this.plotLeft);this.plotTop=j=v(this.plotTop);this.plotWidth=k=u(0,v(d-i-this.marginRight));this.plotHeight=l=u(0,v(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*U(this.plotBorderWidth/2);b=Ka(u(d,h[3])/2);c=Ka(u(d,h[0])/2);this.clipBox={x:b,y:c,width:U(this.plotSizeX- -u(d,h[1])/2-b),height:u(0,U(this.plotSizeY-u(d,h[2])/2-c))};a||q(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=p(b[0],a[0]);this.marginRight=p(b[1],a[1]);this.marginBottom=p(b[2],a[2]);this.plotLeft=p(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g= -this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,n,o=this.plotLeft,p=this.plotTop,q=this.plotWidth,r=this.plotHeight,u=this.plotBox,s=this.clipRect,v=this.clipBox;n=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:j||P};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f? -f.animate(u):this.plotBackground=b.rect(o,p,q,r,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(u):this.plotBGImage=b.image(l,o,p,q,r).add();s?s.animate({width:v.width,height:v.height}):this.clipRect=b.clipRect(v);if(m)g?g.animate(g.crisp({x:o,y:p,width:q,height:r})):this.plotBorder=b.rect(o,p,q,r,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,fill:P,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;q(["inverted", -"angular","polar"],function(g){c=J[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=J[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;q(b,function(a){a.linkedSeries.length=0});q(b,function(b){var d=b.options.linkedTo;if(Fa(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},renderSeries:function(){q(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints(); -a.render()})},renderLabels:function(){var a=this,b=a.options.labels;b.items&&q(b.items,function(c){var d=r(b.style,c.style),e=z(d.left)+a.plotLeft,f=z(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options;this.setTitle();this.legend=new kb(this,c.legend);this.getStacks();q(a,function(a){a.setScale()});this.getMargins();this.maxTicks=null;q(a,function(a){a.setTickPositions(!0);a.setMaxTicks()}); -this.adjustTickAmounts();this.getMargins();this.drawChartBox();this.hasCartesianSeries&&q(a,function(a){a.render()});if(!this.seriesGroup)this.seriesGroup=b.g("series-group").attr({zIndex:3}).add();this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){if(a.enabled&&!this.credits)this.credits=this.renderer.text(a.text,0,0).on("click",function(){if(a.href)location.href=a.href}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position)}, -destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;K(a,"destroy");W[a.index]=t;$a--;a.renderTo.removeAttribute("data-highcharts-chart");X(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();q("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML= -"",X(d),f&&Pa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!ba&&H==H.top&&x.readyState!=="complete"||ga&&!H.canvg?(ga?Lb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):x.attachEvent("onreadystatechange",function(){x.detachEvent("onreadystatechange",a.firstRender);x.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender()){a.getContainer();K(a,"init");a.resetMargins();a.setChartSize(); -a.propFromSeries();a.getAxes();q(b.series||[],function(b){a.initSeries(b)});a.linkSeries();K(a,"beforeRender");if(S.Pointer)a.pointer=new Va(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);q(a.callbacks,function(b){b.apply(a,[a])});a.cloneRenderTo(!0);K(a,"load")}},splashArray:function(a,b){var c=b[a],c=da(c)?c:[c,c,c,c];return[p(b[a+"Top"],c[0]),p(b[a+"Right"],c[1]),p(b[a+"Bottom"],c[2]),p(b[a+"Left"],c[3])]}};Xa.prototype.callbacks=[];Z=S.CenteredSeriesMixin={getCenter:function(){var a=this.options, -b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[p(b[0],"50%"),p(b[1],"50%"),a.size||"100%",a.innerSize||0],g=C(e,f),h;return Ua(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*z(a)/100:a)+(d?c:0)})}};var Ea=function(){};Ea.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter=== -b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.options.pointValKey||c.pointValKey,a=Ea.prototype.optionsToObject.call(this,a);r(this,a);this.options=this.options?r(this.options,a):a;if(d)this.y=this[d];if(this.x===t&&c)this.x=b===t?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b[d[0]]=a;else if(La(a)){if(a.length>e){c=typeof a[0]; -if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;ga+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=w(e,c.series, -a);this.tooltipOptions=w(L.tooltip,L.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(s(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint||this.getCyclic("color",this.options.color||ca[this.type].color,this.chart.options.colors)},getSymbol:function(){var a= -this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);if(/^url/.test(this.symbol))a.radius=0},drawLegendSymbol:M.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,j=e.chart,k=null,l=e.xAxis,m=l&&!!l.categories,n=e.tooltipPoints,o=i.turboThreshold,r=this.xData,u=this.yData,s=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=p(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData)q(a,function(a,b){f[b].update(a,!1)});else{e.xIncrement= -null;e.pointRange=m?1:i.pointRange;e.colorCounter=0;q(this.parallelArrays,function(a){e[a+"Data"].length=0});if(o&&h>o){for(c=0;k===null&&cj||this.forceCrop))if(m= -h.getExtremes(),n=m.min,m=m.max,b[d-1]m)b=[],c=[];else if(b[0]m)e=this.cropData(this.xData,this.yData,n,m),b=e.xData,c=e.yData,e=e.start,f=!0,k=b.length;for(a=b.length-1;a>=0;a--)d=b[a]-b[a-1],!f&&b[a]>n&&b[a]0&&(g===t||d=c){f=u(0,i-h);break}for(;id){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m0),j=this.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&& -(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=p(void 0,Na(e));this.dataMax=p(void 0,Ba(e))},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||ia(i),k=a.threshold,a=0;a0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(n)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=p(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=ca[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],i,j=[],k,l=a.pointAttrToOptions; -k=a.hasPointSpecificOptions;var m=b.negativeColor,n=c.lineColor,o=c.fillColor;i=b.turboThreshold;var p;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):e.color=e.color||ya(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,f);q(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;g=h.length;if(!i||g -1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=b.linecap!=="square",g=this.getGraphPath(),h=b.negativeColor;h&&c.push(["graphNeg",h]);q(c,function(c,h){var k=c[0],l=a[k];if(l)ab(l),l.animate({d:g});else if(d&&g.length)l={stroke:c[1],"stroke-width":d,fill:P,zIndex:1},e?l.dashstyle=e:f&&(l["stroke-linecap"]=l["stroke-linejoin"]="round"),a[k]=a.chart.renderer.path(g).attr(l).add(a.group).shadow(!h&& -b.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=u(e,j),l=this.yAxis;if(d&&(f||g)){d=v(l.toPixels(a.threshold||0,!0));d<0&&(k-=d);a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e}); -l.reversed?(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};q(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)N(c,"resize",a),N(b,"destroy",function(){X(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d, -e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis;if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&p(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex, -h=a.hasRendered,i=b.seriesGroup;c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(),a.clipNeg());a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)e?a.animationTimeout=setTimeout(function(){a.afterAnimate()}, -e):a.afterAnimate();a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:p(d&&d.left,a.plotLeft),translateY:p(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);this.render();b&&K(this,"updatedData")}};Fb.prototype={destroy:function(){Oa(this,this.axis)},render:function(a){var b=this.options, -c=b.format,c=c?Ia(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,null,null,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(c.usePercentage?100:this.total,0,0,0,1),c=c.translate(0),c=Q(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g- -c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e[this.options.crop===!1||d.isInsidePlot(f.x,f.y)?"show":"hide"](!0)}};ma.prototype.buildStacks=function(){var a=this.series,b=p(this.options.reversedStacks,!0),c=a.length;if(!this.isXAxis){for(this.usePercentage=!1;c--;)a[b?c:a.length-c-1].setStackedPoints();if(this.usePercentage)for(c=0;cg;)h--;this.updateParallelArrays(d,"splice",h,0,0);this.updateParallelArrays(d,h);if(j)j[g]=d.name;l.splice(h,0,a);m&&(this.data.splice(h,0,null),this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),this.updateParallelArrays(d,"shift"),l.shift()));this.isDirtyData=this.isDirty= -!0;b&&(this.getAttribs(),i.redraw())},remove:function(a,b){var c=this,d=c.chart,a=p(a,!0);if(!c.isRemoving)c.isRemoving=!0,K(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=J[f].prototype,h=["group","markerGroup","dataLabelsGroup"],i;q(h,function(a){h[a]=c[a];delete c[a]});a=w(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data}, -a);this.remove(!1);for(i in g)g.hasOwnProperty(i)&&(this[i]=t);r(this,J[a.type||f].prototype);q(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries();p(b,!0)&&d.redraw(!1)}});r(ma.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=w(this.userOptions,a);this.destroy(!0);this._addedPlotLB=t;this.init(c,r(a,{events:t}));c.isDirtyBox=!0;p(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1); -ka(b.axes,this);ka(b[c],this);b.options[c].splice(this.options.index,1);q(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;p(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});ha=la(O);J.line=ha;ca.area=w(T,{threshold:0});var qa=la(O,{type:"area",getSegments:function(){var a=this,b=[],c=[],d=[],e=this.xAxis,f=this.yAxis,g=f.stacks[this.stackKey],h={},i,j,k=this.points,l=this.options.connectNulls,m,n; -if(this.options.stacking&&!this.cropped){for(m=0;m=0;d--)g=p(a[d].yBottom,f),da&&i>e?(i=u(a,e),k=2*e-i):ig&&k>e?(k=u(g,e),i=2*e-k):k0.5*a.xAxis.len?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=p(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width,j=a.barW=u(i,1+2*d),k=a.pointXOffset=h.offset,l=-(d%2?0.5:0),m=d%2?0.5:1;b.renderer.isVML&&b.inverted&&(m+=1);c.pointPadding&&(j=Ka(j));O.prototype.translate.apply(a);q(a.points,function(c){var d=p(c.yBottom,f),h=C(u(-999-d,c.plotY),e.len+999+d),q=c.plotX+k,r=j,s=C(h,d),t;t=u(h,d)-s;Q(t)g?d-g:f-(e.translate(c.y, -0,1,0,1)<=f?g:0)));c.barX=q;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len-h,a.xAxis.len-q-r/2]:[q+r/2,h];r=v(q+r)+l;q=v(q)+l;r-=q;d=Q(s)<0.5;t=v(s+t)+m;s=v(s)+m;t-=s;d&&(s-=1,t+=1);c.shapeType="rect";c.shapeArgs={x:q,y:s,width:r,height:t}})},getSymbol:sa,drawLegendSymbol:M.drawRectangle,drawGraph:sa,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit||250,f,g;q(a.points,function(h){var i=h.plotY,j=h.graphic;if(i!==t&&!isNaN(i)&&h.y!==null)f=h.shapeArgs,i=s(a.borderWidth)? -{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],j?(ab(j),j.attr(i)[b.pointCount {series.name}
    ',pointFormat:"x: {point.x}
    y: {point.y}
    "}, -stickyTracking:!1});qa=la(O,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,singularTooltips:!0,drawGraph:function(){this.options.lineWidth&&O.prototype.drawGraph.call(this)}});J.scatter=qa;ca.pie=w(T,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null, -showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});T={type:"pie",isCartesian:!1,pointClass:la(Ea,{init:function(){Ea.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;r(a,{visible:a.visible!==!1,name:p(a.name,"Slice")});b=function(b){a.slice(b.type==="select")};N(a,"select",b);N(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart;b.visible=b.options.visible=a=a===t?!b.visible:a;c.options.data[Da(b, -c.data)]=b.options;q(["graphic","dataLabel","connector","shadowGroup"],function(c){if(b[c])b[c][a?"show":"hide"](!0)});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;Qa(c,d.chart);p(b,!0);this.sliced=this.options.sliced=a=s(a)?a:!this.sliced;d.options.data[Da(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}, -haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},singularTooltips:!0,getColor:sa,animate:function(a){var b=this,c=b.points,d=b.startAngleRad; -if(!a)q(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b,c,d){O.prototype.setData.call(this,a,!1,c,d);this.processData();this.generatePoints();p(b,!0)&&this.chart.redraw(c)},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;O.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a0?e.y/b*100:0,e.total=b},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=na/180*(i-90),i=(this.endAngleRad=na/180*(p(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=V.asin(C((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*aa(h)*(a[2]/2+l)};for(m=0;m< -n;m++){o=k[m];f=j+b*i;if(!c||o.visible)b+=o.percentage/100;g=j+b*i;o.shapeType="arc";o.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:v(f*1E3)/1E3,end:v(g*1E3)/1E3};h=(g+f)/2;h>1.5*na?h-=2*na:h<-na/2&&(h+=2*na);o.slicedTranslation={translateX:v(aa(h)*d),translateY:v(fa(h)*d)};f=aa(h)*a[2]/2;g=fa(h)*a[2]/2;o.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];o.half=h<-na/2||h>na/2?1:0;o.angle=h;e=C(e,l/2);o.labelPos=[a[0]+f+aa(h)*l,a[1]+g+fa(h)*l,a[0]+f+aa(h)*e,a[1]+g+fa(h)*e,a[0]+f,a[1]+g,l<0?"center":o.half? -"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);q(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(r(g,c)):h.graphic=d=b[h.shapeType](g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select":""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e, -f);h.visible!==void 0&&h.setVisible(h.visible)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:M.drawRectangle,getCenter:Z.getCenter,getSymbol:sa};T=la(O,T);J.pie=T;O.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h,i;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),i=a.plotGroup("dataLabelsGroup","data-labels",d.defer?"hidden":"visible",d.zIndex||6),!a.hasRendered&& -p(d.defer,!0)&&(i.attr({opacity:0}),N(a,"afterAnimate",function(){a.visible&&i.show();i[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,q(e,function(b){var e,l=b.dataLabel,m,n,o=b.connector,q=!0;f=b.options&&b.options.dataLabels;e=p(f&&f.enabled,g.enabled);if(l&&!e)b.dataLabel=l.destroy();else if(e){d=w(g,f);e=d.rotation;m=b.getLabelConfig();h=d.format?Ia(d.format,m):d.formatter.call(m,d);d.style.color=p(d.color,d.style.color,a.color,"black");if(l)if(s(h))l.attr({text:h}),q=!1;else{if(b.dataLabel= -l=l.destroy(),o)b.connector=o.destroy()}else if(s(h)){l={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(n in l)l[n]===t&&delete l[n];l=b.dataLabel=a.chart.renderer[e?"text":"label"](h,0,-999,null,null,null,d.useHTML).attr(l).css(r(d.style,c&&{cursor:c})).add(i).shadow(d.shadow)}l&&a.alignDataLabel(b,l,d,null,q)}})};O.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=p(a.plotX,-999), -i=p(a.plotY,-999),j=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,v(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)))d=r({x:g?f.plotWidth-i:h,y:v(g?f.plotHeight-h:i),width:0,height:0},d),r(c,{width:j.width,height:j.height}),c.rotation?b[e?"attr":"animate"]({x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2}).attr({align:c.align}):(b.align(c,null,d),g=b.alignAttr,p(c.overflow,"justify")==="justify"?this.justifyDataLabel(b,c,g,j,d,e):p(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+ -j.width,g.y+j.height)));if(!a)b.attr({y:-999}),b.placed=!1};O.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(J.pie)J.pie.prototype.drawDataLabels= -function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=p(e.connectorPadding,10),g=p(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,j,k,l=p(e.softConnector,!0),m=e.distance,n=a.center,o=n[2]/2,r=n[1],s=m>0,t,w,x,z=[[],[]],B,A,K,J,y,R=[0,0,0,0],N=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){O.prototype.drawDataLabels.apply(a);q(b,function(a){a.dataLabel&&a.visible&&z[a.half].push(a)});for(J=2;J--;){var H=[],M=[],F=z[J],L=F.length,G;if(L){a.sortByAngle(F, -J-0.5);for(y=b=0;!b&&F[y];)b=F[y]&&F[y].dataLabel&&(F[y].dataLabel.getBBox().height||21),y++;if(m>0){w=C(r+o+m,d.plotHeight);for(y=u(0,r-o-m);y<=w;y+=b)H.push(y);w=H.length;if(L>w){c=[].concat(F);c.sort(N);for(y=L;y--;)c[y].rank=y;for(y=L;y--;)F[y].rank>=w&&F.splice(y,1);L=F.length}for(y=0;y0){if(w=M.pop(),G=w.i,A=w.y,c>A&&H[G+1]!==null||ch-f&&(R[1]=u(v(B+w-h+f),R[1])),A-b/2<0?R[0]=u(v(-A+b/2),R[0]): -A+b/2>i&&(R[2]=u(v(A+b/2-i),R[2]))}}}if(Ba(R)===0||this.verifyDataLabelOverflow(R))this.placeDataLabels(),s&&g&&q(this.points,function(b){j=b.connector;x=b.labelPos;if((t=b.dataLabel)&&t._pos)K=t._attr.visibility,B=t.connX,A=t.connY,k=l?["M",B+(x[6]==="left"?5:-5),A,"C",B,A,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",B+(x[6]==="left"?5:-5),A,"L",x[2],x[3],"L",x[4],x[5]],j?(j.animate({d:k}),j.attr("visibility",K)):b.connector=j=a.chart.renderer.path(k).attr({"stroke-width":g,stroke:e.connectorColor|| -b.color||"#606060",visibility:K}).add(a.dataLabelsGroup);else if(j)b.connector=j.destroy()})}},J.pie.prototype.placeDataLabels=function(){q(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},J.pie.prototype.alignDataLabel=sa,J.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=u(b[2]-u(a[1],a[3]),c):(e=u(b[2]-a[1]-a[3],c),b[0]+=(a[3]- -a[1])/2);d[1]!==null?e=u(C(e,b[2]-u(a[0],a[2])),c):(e=u(C(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);ep(this.translatedThreshold,f.plotSizeY),j=p(c.inside,!!this.options.stacking);if(h&&(d=w(h),g&&(d={x:f.plotWidth- -d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=p(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=p(c.verticalAlign,g||j?"middle":i?"top":"bottom");O.prototype.alignDataLabel.call(this,a,b,c,d,e)};T=S.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point, -d=d.parentNode;if(e!==t&&e!==b.hoverPoint)e.onMouseOver(c)};q(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)q(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),Za))a[b].on("touchstart",f)}),a._hasTracking=!0},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length, -f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,n=function(){if(f.hoverSeries!==a)a.onMouseOver()},o="rgba(192,192,192,"+(ba?1.0E-4:0.002)+")";if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;mC(k.dataMin,k.min)&&i=f.min&&c<=f.max){h=b[i+1];c=d===t?0:d+1;for(d=b[i+1]?C(u(0, -U((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===t?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;K(this,a?"select":"unselect")},drawTracker:T.drawTrackerGraph});r(S,{Axis:ma,Chart:Xa,Color:ya,Point:Ea,Tick:Sa,Renderer:Ya,Series:O,SVGElement:G,SVGRenderer:ta,arrayMin:Na,arrayMax:Ba,charts:W,dateFormat:bb,format:Ia,pathAnim:ub, -getOptions:function(){return L},hasBidiBug:Nb,isTouchDevice:Hb,numberFormat:Ga,seriesTypes:J,setOptions:function(a){L=w(!0,L,a);Ab();return L},addEvent:N,removeEvent:X,createElement:$,discardElement:Pa,css:A,each:q,extend:r,map:Ua,merge:w,pick:p,splat:ra,extendClass:la,pInt:z,wrap:Ma,svg:ba,canvas:ga,vml:!ba&&!ga,product:"Highcharts",version:"4.0.3"})})(); diff --git a/public/assets/stylesheets/highslide/highslide.css b/public/assets/stylesheets/highslide/highslide.css deleted file mode 100755 index c882a43145..0000000000 --- a/public/assets/stylesheets/highslide/highslide.css +++ /dev/null @@ -1,889 +0,0 @@ -/** -* @file: highslide.css -* @version: 4.1.13 -*/ -.highslide-container div { - font-family: Verdana, Helvetica; - font-size: 10pt; -} -.highslide-container table { - background: none; -} -.highslide { - outline: none; - text-decoration: none; -} -.highslide img { - border: 2px solid silver; -} -.highslide:hover img { - border-color: gray; -} -.highslide-active-anchor img { - visibility: hidden; -} -.highslide-gallery .highslide-active-anchor img { - border-color: black; - visibility: visible; - cursor: default; -} -.highslide-image { - border-width: 2px; - border-style: solid; - border-color: white; -} -.highslide-wrapper, .highslide-outline { - background: white; -} -.glossy-dark { - background: #111; -} - -.highslide-image-blur { -} -.highslide-number { - font-weight: bold; - color: gray; - font-size: .9em; -} -.highslide-caption { - display: none; - font-size: 1em; - padding: 5px; - /*background: white;*/ -} -.highslide-heading { - display: none; - font-weight: bold; - margin: 0.4em; -} -.highslide-dimming { - /*position: absolute;*/ - background: black; -} -a.highslide-full-expand { - background: url(highslide/fullexpand.gif) no-repeat; - display: block; - margin: 0 10px 10px 0; - width: 34px; - height: 34px; -} -.highslide-loading { - display: block; - color: black; - font-size: 9px; - font-weight: bold; - text-transform: uppercase; - text-decoration: none; - padding: 3px; - border: 1px solid white; - background-color: white; - padding-left: 22px; - background-image: url(highslide/loader.white.gif); - background-repeat: no-repeat; - background-position: 3px 1px; -} -a.highslide-credits, -a.highslide-credits i { - padding: 2px; - color: silver; - text-decoration: none; - font-size: 10px; -} -a.highslide-credits:hover, -a.highslide-credits:hover i { - color: white; - background-color: gray; -} -.highslide-move, .highslide-move * { - cursor: move; -} - -.highslide-viewport { - display: none; - position: fixed; - width: 100%; - height: 100%; - z-index: 1; - background: none; - left: 0; - top: 0; -} -.highslide-overlay { - display: none; -} -.hidden-container { - display: none; -} -/* Example of a semitransparent, offset closebutton */ -.closebutton { - position: relative; - top: -15px; - left: 15px; - width: 30px; - height: 30px; - cursor: pointer; - background: url(highslide/close.png); - /* NOTE! For IE6, you also need to update the highslide-ie6.css file. */ -} - -/*****************************************************************************/ -/* Thumbnail boxes for the galleries. */ -/* Remove these if you are not using a gallery. */ -/*****************************************************************************/ -.highslide-gallery ul { - list-style-type: none; - margin: 0; - padding: 0; -} -.highslide-gallery ul li { - display: block; - position: relative; - float: left; - width: 106px; - height: 106px; - border: 1px solid silver; - background: #ededed; - margin: 2px; - padding: 0; - line-height: 0; - overflow: hidden; -} -.highslide-gallery ul a { - position: absolute; - top: 50%; - left: 50%; -} -.highslide-gallery ul img { - position: relative; - top: -50%; - left: -50%; -} -html>/**/body .highslide-gallery ul li { - display: table; - text-align: center; -} -html>/**/body .highslide-gallery ul li { - text-align: center; -} -html>/**/body .highslide-gallery ul a { - position: static; - display: table-cell; - vertical-align: middle; -} -html>/**/body .highslide-gallery ul img { - position: static; -} - -/*****************************************************************************/ -/* Controls for the galleries. */ -/* Remove these if you are not using a gallery */ -/*****************************************************************************/ -.highslide-controls { - width: 195px; - height: 40px; - background: url(highslide/controlbar-white.gif) 0 -90px no-repeat; - margin: 20px 15px 10px 0; -} -.highslide-controls ul { - position: relative; - left: 15px; - height: 40px; - list-style: none; - margin: 0; - padding: 0; - background: url(highslide/controlbar-white.gif) right -90px no-repeat; - -} -.highslide-controls li { - float: left; - padding: 5px 0; - margin:0; - list-style: none; -} -.highslide-controls a { - background-image: url(highslide/controlbar-white.gif); - display: block; - float: left; - height: 30px; - width: 30px; - outline: none; -} -.highslide-controls a.disabled { - cursor: default; -} -.highslide-controls a.disabled span { - cursor: default; -} -.highslide-controls a span { - /* hide the text for these graphic buttons */ - display: none; - cursor: pointer; -} - - -/* The CSS sprites for the controlbar - see http://www.google.com/search?q=css+sprites */ -.highslide-controls .highslide-previous a { - background-position: 0 0; -} -.highslide-controls .highslide-previous a:hover { - background-position: 0 -30px; -} -.highslide-controls .highslide-previous a.disabled { - background-position: 0 -60px !important; -} -.highslide-controls .highslide-play a { - background-position: -30px 0; -} -.highslide-controls .highslide-play a:hover { - background-position: -30px -30px; -} -.highslide-controls .highslide-play a.disabled { - background-position: -30px -60px !important; -} -.highslide-controls .highslide-pause a { - background-position: -60px 0; -} -.highslide-controls .highslide-pause a:hover { - background-position: -60px -30px; -} -.highslide-controls .highslide-next a { - background-position: -90px 0; -} -.highslide-controls .highslide-next a:hover { - background-position: -90px -30px; -} -.highslide-controls .highslide-next a.disabled { - background-position: -90px -60px !important; -} -.highslide-controls .highslide-move a { - background-position: -120px 0; -} -.highslide-controls .highslide-move a:hover { - background-position: -120px -30px; -} -.highslide-controls .highslide-full-expand a { - background-position: -150px 0; -} -.highslide-controls .highslide-full-expand a:hover { - background-position: -150px -30px; -} -.highslide-controls .highslide-full-expand a.disabled { - background-position: -150px -60px !important; -} -.highslide-controls .highslide-close a { - background-position: -180px 0; -} -.highslide-controls .highslide-close a:hover { - background-position: -180px -30px; -} - -/*****************************************************************************/ -/* Styles for the HTML popups */ -/* Remove these if you are not using Highslide HTML */ -/*****************************************************************************/ -.highslide-maincontent { - display: none; -} -.highslide-html { - background-color: white; -} -.mobile .highslide-html { - border: 1px solid silver; -} -.highslide-html-content { - display: none; - width: 400px; - padding: 0 5px 5px 5px; -} -.highslide-header { - padding-bottom: 5px; -} -.highslide-header ul { - margin: 0; - padding: 0; - text-align: right; -} -.highslide-header ul li { - display: inline; - padding-left: 1em; -} -.highslide-header ul li.highslide-previous, .highslide-header ul li.highslide-next { - display: none; -} -.highslide-header a { - font-weight: bold; - color: gray; - text-transform: uppercase; - text-decoration: none; -} -.highslide-header a:hover { - color: black; -} -.highslide-header .highslide-move a { - cursor: move; -} -.highslide-footer { - height: 16px; -} -.highslide-footer .highslide-resize { - display: block; - float: right; - margin-top: 5px; - height: 11px; - width: 11px; - background: url(highslide/resize.gif) no-repeat; -} -.highslide-footer .highslide-resize span { - display: none; -} -.highslide-body { -} -.highslide-resize { - cursor: nw-resize; -} - -/*****************************************************************************/ -/* Styles for the Individual wrapper class names. */ -/* See www.highslide.com/ref/hs.wrapperClassName */ -/* You can safely remove the class name themes you don't use */ -/*****************************************************************************/ - -/* hs.wrapperClassName = 'draggable-header' */ -.draggable-header .highslide-header { - height: 18px; - border-bottom: 1px solid #dddddd; -} -.draggable-header .highslide-heading { - position: absolute; - margin: 2px 0.4em; -} - -.draggable-header .highslide-header .highslide-move { - cursor: move; - display: block; - height: 16px; - position: absolute; - right: 24px; - top: 0; - width: 100%; - z-index: 1; -} -.draggable-header .highslide-header .highslide-move * { - display: none; -} -.draggable-header .highslide-header .highslide-close { - position: absolute; - right: 2px; - top: 2px; - z-index: 5; - padding: 0; -} -.draggable-header .highslide-header .highslide-close a { - display: block; - height: 16px; - width: 16px; - background-image: url(highslide/closeX.png); -} -.draggable-header .highslide-header .highslide-close a:hover { - background-position: 0 16px; -} -.draggable-header .highslide-header .highslide-close span { - display: none; -} -.draggable-header .highslide-maincontent { - padding-top: 1em; -} - -/* hs.wrapperClassName = 'titlebar' */ -.titlebar .highslide-header { - height: 18px; - border-bottom: 1px solid #dddddd; -} -.titlebar .highslide-heading { - position: absolute; - width: 90%; - margin: 1px 0 1px 5px; - color: #666666; -} - -.titlebar .highslide-header .highslide-move { - cursor: move; - display: block; - height: 16px; - position: absolute; - right: 24px; - top: 0; - width: 100%; - z-index: 1; -} -.titlebar .highslide-header .highslide-move * { - display: none; -} -.titlebar .highslide-header li { - position: relative; - top: 3px; - z-index: 2; - padding: 0 0 0 1em; -} -.titlebar .highslide-maincontent { - padding-top: 1em; -} - -/* hs.wrapperClassName = 'no-footer' */ -.no-footer .highslide-footer { - display: none; -} - -/* hs.wrapperClassName = 'wide-border' */ -.wide-border { - background: white; -} -.wide-border .highslide-image { - border-width: 10px; -} -.wide-border .highslide-caption { - padding: 0 10px 10px 10px; -} - -/* hs.wrapperClassName = 'borderless' */ -.borderless .highslide-image { - border: none; -} -.borderless .highslide-caption { - border-bottom: 1px solid white; - border-top: 1px solid white; - background: silver; -} - -/* hs.wrapperClassName = 'outer-glow' */ -.outer-glow { - background: #444; -} -.outer-glow .highslide-image { - border: 5px solid #444444; -} -.outer-glow .highslide-caption { - border: 5px solid #444444; - border-top: none; - padding: 5px; - background-color: gray; -} - -/* hs.wrapperClassName = 'colored-border' */ -.colored-border { - background: white; -} -.colored-border .highslide-image { - border: 2px solid green; -} -.colored-border .highslide-caption { - border: 2px solid green; - border-top: none; -} - -/* hs.wrapperClassName = 'dark' */ -.dark { - background: #111; -} -.dark .highslide-image { - border-color: black black #202020 black; - background: gray; -} -.dark .highslide-caption { - color: white; - background: #111; -} -.dark .highslide-controls, -.dark .highslide-controls ul, -.dark .highslide-controls a { - background-image: url(highslide/controlbar-black-border.gif); -} - -/* hs.wrapperClassName = 'floating-caption' */ -.floating-caption .highslide-caption { - position: absolute; - padding: 1em 0 0 0; - background: none; - color: white; - border: none; - font-weight: bold; -} - -/* hs.wrapperClassName = 'controls-in-heading' */ -.controls-in-heading .highslide-heading { - color: gray; - font-weight: bold; - height: 20px; - overflow: hidden; - cursor: default; - padding: 0 0 0 22px; - margin: 0; - background: url(highslide/icon.gif) no-repeat 0 1px; -} -.controls-in-heading .highslide-controls { - width: 105px; - height: 20px; - position: relative; - margin: 0; - top: -23px; - left: 7px; - background: none; -} -.controls-in-heading .highslide-controls ul { - position: static; - height: 20px; - background: none; -} -.controls-in-heading .highslide-controls li { - padding: 0; -} -.controls-in-heading .highslide-controls a { - background-image: url(highslide/controlbar-white-small.gif); - height: 20px; - width: 20px; -} - -.controls-in-heading .highslide-controls .highslide-move { - display: none; -} - -.controls-in-heading .highslide-controls .highslide-previous a { - background-position: 0 0; -} -.controls-in-heading .highslide-controls .highslide-previous a:hover { - background-position: 0 -20px; -} -.controls-in-heading .highslide-controls .highslide-previous a.disabled { - background-position: 0 -40px !important; -} -.controls-in-heading .highslide-controls .highslide-play a { - background-position: -20px 0; -} -.controls-in-heading .highslide-controls .highslide-play a:hover { - background-position: -20px -20px; -} -.controls-in-heading .highslide-controls .highslide-play a.disabled { - background-position: -20px -40px !important; -} -.controls-in-heading .highslide-controls .highslide-pause a { - background-position: -40px 0; -} -.controls-in-heading .highslide-controls .highslide-pause a:hover { - background-position: -40px -20px; -} -.controls-in-heading .highslide-controls .highslide-next a { - background-position: -60px 0; -} -.controls-in-heading .highslide-controls .highslide-next a:hover { - background-position: -60px -20px; -} -.controls-in-heading .highslide-controls .highslide-next a.disabled { - background-position: -60px -40px !important; -} -.controls-in-heading .highslide-controls .highslide-full-expand a { - background-position: -100px 0; -} -.controls-in-heading .highslide-controls .highslide-full-expand a:hover { - background-position: -100px -20px; -} -.controls-in-heading .highslide-controls .highslide-full-expand a.disabled { - background-position: -100px -40px !important; -} -.controls-in-heading .highslide-controls .highslide-close a { - background-position: -120px 0; -} -.controls-in-heading .highslide-controls .highslide-close a:hover { - background-position: -120px -20px; -} - -/*****************************************************************************/ -/* Styles for text based controls. */ -/* You can safely remove this if you don't use text based controls */ -/*****************************************************************************/ - -.text-controls .highslide-controls { - width: auto; - height: auto; - margin: 0; - text-align: center; - background: none; -} -.text-controls ul { - position: static; - background: none; - height: auto; - left: 0; -} -.text-controls .highslide-move { - display: none; -} -.text-controls li { - background-image: url(highslide/controlbar-text-buttons.png); - background-position: right top !important; - padding: 0; - margin-left: 15px; - display: block; - width: auto; -} -.text-controls a { - background: url(highslide/controlbar-text-buttons.png) no-repeat; - background-position: left top !important; - position: relative; - left: -10px; - display: block; - width: auto; - height: auto; - text-decoration: none !important; -} -.text-controls a span { - background: url(highslide/controlbar-text-buttons.png) no-repeat; - margin: 1px 2px 1px 10px; - display: block; - min-width: 4em; - height: 18px; - line-height: 18px; - padding: 1px 0 1px 18px; - color: #333; - font-family: "Trebuchet MS", Arial, sans-serif; - font-size: 12px; - font-weight: bold; - white-space: nowrap; -} -.text-controls .highslide-next { - margin-right: 1em; -} -.text-controls .highslide-full-expand a span { - min-width: 0; - margin: 1px 0; - padding: 1px 0 1px 10px; -} -.text-controls .highslide-close a span { - min-width: 0; -} -.text-controls a:hover span { - color: black; -} -.text-controls a.disabled span { - color: #999; -} - -.text-controls .highslide-previous span { - background-position: 0 -40px; -} -.text-controls .highslide-previous a.disabled { - background-position: left top !important; -} -.text-controls .highslide-previous a.disabled span { - background-position: 0 -140px; -} -.text-controls .highslide-play span { - background-position: 0 -60px; -} -.text-controls .highslide-play a.disabled { - background-position: left top !important; -} -.text-controls .highslide-play a.disabled span { - background-position: 0 -160px; -} -.text-controls .highslide-pause span { - background-position: 0 -80px; -} -.text-controls .highslide-next span { - background-position: 0 -100px; -} -.text-controls .highslide-next a.disabled { - background-position: left top !important; -} -.text-controls .highslide-next a.disabled span { - background-position: 0 -200px; -} -.text-controls .highslide-full-expand span { - background: none; -} -.text-controls .highslide-full-expand a.disabled { - background-position: left top !important; -} -.text-controls .highslide-close span { - background-position: 0 -120px; -} - - -/*****************************************************************************/ -/* Styles for the thumbstrip. */ -/* See www.highslide.com/ref/hs.addSlideshow */ -/* You can safely remove this if you don't use a thumbstrip */ -/*****************************************************************************/ - -.highslide-thumbstrip { - height: 100%; - direction: ltr; -} -.highslide-thumbstrip div { - overflow: hidden; -} -.highslide-thumbstrip table { - position: relative; - padding: 0; - border-collapse: collapse; -} -.highslide-thumbstrip td { - padding: 1px; - /*text-align: center;*/ -} -.highslide-thumbstrip a { - outline: none; -} -.highslide-thumbstrip img { - display: block; - border: 1px solid gray; - margin: 0 auto; -} -.highslide-thumbstrip .highslide-active-anchor img { - visibility: visible; -} -.highslide-thumbstrip .highslide-marker { - position: absolute; - width: 0; - height: 0; - border-width: 0; - border-style: solid; - border-color: transparent; /* change this to actual background color in highslide-ie6.css */ -} -.highslide-thumbstrip-horizontal div { - width: auto; - /* width: 100% breaks in small strips in IE */ -} -.highslide-thumbstrip-horizontal .highslide-scroll-up { - display: none; - position: absolute; - top: 3px; - left: 3px; - width: 25px; - height: 42px; -} -.highslide-thumbstrip-horizontal .highslide-scroll-up div { - margin-bottom: 10px; - cursor: pointer; - background: url(highslide/scrollarrows.png) left center no-repeat; - height: 42px; -} -.highslide-thumbstrip-horizontal .highslide-scroll-down { - display: none; - position: absolute; - top: 3px; - right: 3px; - width: 25px; - height: 42px; -} -.highslide-thumbstrip-horizontal .highslide-scroll-down div { - margin-bottom: 10px; - cursor: pointer; - background: url(highslide/scrollarrows.png) center right no-repeat; - height: 42px; -} -.highslide-thumbstrip-horizontal table { - margin: 2px 0 10px 0; -} -.highslide-viewport .highslide-thumbstrip-horizontal table { - margin-left: 10px; -} -.highslide-thumbstrip-horizontal img { - width: auto; - height: 40px; -} -.highslide-thumbstrip-horizontal .highslide-marker { - top: 47px; - border-left-width: 6px; - border-right-width: 6px; - border-bottom: 6px solid gray; -} -.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker { - margin-left: 10px; -} -.dark .highslide-thumbstrip-horizontal .highslide-marker, .highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker { - border-bottom-color: white !important; -} - -.highslide-thumbstrip-vertical-overlay { - overflow: hidden !important; -} -.highslide-thumbstrip-vertical div { - height: 100%; -} -.highslide-thumbstrip-vertical a { - display: block; -} -.highslide-thumbstrip-vertical .highslide-scroll-up { - display: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 25px; -} -.highslide-thumbstrip-vertical .highslide-scroll-up div { - margin-left: 10px; - cursor: pointer; - background: url(highslide/scrollarrows.png) top center no-repeat; - height: 25px; -} -.highslide-thumbstrip-vertical .highslide-scroll-down { - display: none; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 25px; -} -.highslide-thumbstrip-vertical .highslide-scroll-down div { - margin-left: 10px; - cursor: pointer; - background: url(highslide/scrollarrows.png) bottom center no-repeat; - height: 25px; -} -.highslide-thumbstrip-vertical table { - margin: 10px 0 0 10px; -} -.highslide-thumbstrip-vertical img { - width: 60px; /* t=5481 */ -} -.highslide-thumbstrip-vertical .highslide-marker { - left: 0; - margin-top: 8px; - border-top-width: 6px; - border-bottom-width: 6px; - border-left: 6px solid gray; -} -.dark .highslide-thumbstrip-vertical .highslide-marker, .highslide-viewport .highslide-thumbstrip-vertical .highslide-marker { - border-left-color: white; -} - -.highslide-viewport .highslide-thumbstrip-float { - overflow: auto; -} -.highslide-thumbstrip-float ul { - margin: 2px 0; - padding: 0; -} -.highslide-thumbstrip-float li { - display: block; - height: 60px; - margin: 0 2px; - list-style: none; - float: left; -} -.highslide-thumbstrip-float img { - display: inline; - border-color: silver; - max-height: 56px; -} -.highslide-thumbstrip-float .highslide-active-anchor img { - border-color: black; -} -.highslide-thumbstrip-float .highslide-scroll-up div, .highslide-thumbstrip-float .highslide-scroll-down div { - display: none; -} -.highslide-thumbstrip-float .highslide-marker { - display: none; -} \ No newline at end of file From 8ad0d7af9317eb8829356aa42d07cdf81656f3e5 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Tue, 28 Oct 2014 16:29:31 +0100 Subject: [PATCH 21/57] Update views and CSS --- app/views/index.blade.php | 185 ++++++++++++----------- public/assets/stylesheets/sbadmin/sb.css | 4 + 2 files changed, 99 insertions(+), 90 deletions(-) diff --git a/app/views/index.blade.php b/app/views/index.blade.php index c9137158cc..9811649d62 100644 --- a/app/views/index.blade.php +++ b/app/views/index.blade.php @@ -30,102 +30,107 @@ -
    -
    - -
    - -
    -
    -
    +
    +
    + +
    + - -
    - -
    -
    -
    +
    +
    - -
    - -
    -
    -
    -
    -
    -
    - Savings -
    -
    - Bla bla -
    -
    - - -
    -
    - - @include('partials.date_nav') - - -
    -
    - Recurring transactions -
    -
    -
    -
    + +
    + - - - @foreach($transactions as $data) -
    -
    - - {{{$data[1]->name}}} - - - -
    -
    - - -
    -
    - - - -
    -
    - @include('transactions.journals-small-index',['transactions' => $data[0],'account' => $data[1]]) -
    +
    +
    - @endforeach
    + +
    + +
    +
    +
    +
    +
    +
    + Savings +
    +
    + (todo) +
    +
    + + +
    +
    + + @include('partials.date_nav') - @endif + +
    +
    + Recurring transactions +
    +
    +
    +
    +
    - @stop - @section('scripts') - {{HTML::script('assets/javascript/highcharts/highcharts.js')}} - {{HTML::script('assets/javascript/firefly/index.js')}} - @stop - @section('styles') - {{HTML::style('assets/stylesheets/highslide/highslide.css')}} - @stop \ No newline at end of file + + @foreach($transactions as $data) +
    +
    + + {{{$data[1]->name}}} + + + +
    +
    + + +
    +
    + + + +
    +
    + @include('transactions.journals-small-index',['transactions' => $data[0],'account' => $data[1]]) +
    +
    + @endforeach +
    +
    + +@endif + +@stop +@section('scripts') + + +{{HTML::script('assets/javascript/firefly/gcharts.options.js')}} +{{HTML::script('assets/javascript/firefly/gcharts.js')}} + + + +{{HTML::script('assets/javascript/firefly/index.js')}} +@stop +@section('styles') +@stop \ No newline at end of file diff --git a/public/assets/stylesheets/sbadmin/sb.css b/public/assets/stylesheets/sbadmin/sb.css index 151ee76cfe..2e181df17b 100755 --- a/public/assets/stylesheets/sbadmin/sb.css +++ b/public/assets/stylesheets/sbadmin/sb.css @@ -8,6 +8,10 @@ body { background-color: #f8f8f8; } +.google-chart-error { + border:1px red solid;height:20px; +} + #wrapper { width: 100%; } From 2f9c3830041b9508768303853c27d4f15f56e925 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 29 Oct 2014 10:30:52 +0100 Subject: [PATCH 22/57] All new stufs! --- app/config/app.php | 2 +- app/controllers/GoogleChartController.php | 257 +++++++++++++++++- app/lib/FireflyIII/Database/Account.php | 13 + app/lib/FireflyIII/Database/Budget.php | 155 +++++++++++ .../FireflyIII/Database/BudgetInterface.php | 31 +++ app/lib/FireflyIII/Database/Category.php | 115 ++++++++ .../FireflyIII/Database/CategoryInterface.php | 16 ++ .../Database/CommonDatabaseCalls.php | 8 + app/lib/FireflyIII/Database/Recurring.php | 129 +++++++++ .../Database/RecurringInterface.php | 22 ++ .../Database/TransactionJournal.php | 21 ++ .../Database/TransactionJournalInterface.php | 9 + .../Shared/Preferences/Preferences.php | 58 ++++ .../Preferences/PreferencesInterface.php | 29 ++ app/lib/FireflyIII/Shared/Toolkit/Date.php | 86 ++++++ app/routes.php | 11 +- public/assets/javascript/firefly/gcharts.js | 103 ++++++- .../javascript/firefly/gcharts.options.js | 56 ++++ public/assets/javascript/firefly/index.js | 3 + 19 files changed, 1114 insertions(+), 10 deletions(-) create mode 100644 app/lib/FireflyIII/Database/Budget.php create mode 100644 app/lib/FireflyIII/Database/BudgetInterface.php create mode 100644 app/lib/FireflyIII/Database/Category.php create mode 100644 app/lib/FireflyIII/Database/CategoryInterface.php create mode 100644 app/lib/FireflyIII/Database/Recurring.php create mode 100644 app/lib/FireflyIII/Database/RecurringInterface.php create mode 100644 app/lib/FireflyIII/Shared/Preferences/Preferences.php create mode 100644 app/lib/FireflyIII/Shared/Preferences/PreferencesInterface.php create mode 100644 app/lib/FireflyIII/Shared/Toolkit/Date.php diff --git a/app/config/app.php b/app/config/app.php index 66190ce4e2..b997979dd7 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -43,7 +43,7 @@ return [ 'Firefly\Helper\HelperServiceProvider', 'Firefly\Validation\ValidationServiceProvider', 'DaveJamesMiller\Breadcrumbs\ServiceProvider', - 'TwigBridge\ServiceProvider' + 'Grumpydictator\Gchart\GchartServiceProvider', ], 'manifest' => storage_path() . '/meta', 'aliases' => [ diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index ce36712823..c4e5ab89d8 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -1,8 +1,263 @@ addColumn('Day of the month', 'date'); + + /** @var \FireflyIII\Shared\Preferences\Preferences $preferences */ + $preferences = App::make('FireflyIII\Shared\Preferences\Preferences'); + $pref = $preferences->get('frontpageAccounts'); + + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + $accounts = $acct->getByIds($pref->data); + + + /* + * Add a column for each account. + */ + /** @var Account $account */ + foreach ($accounts as $account) { + $chart->addColumn('Balance for ' . $account->name, 'number'); + } + /* + * Loop the date, then loop the accounts, then add balance. + */ + $start = Session::get('start'); + $end = Session::get('end'); + $current = clone $start; + + while ($end >= $current) { + $row = [clone $current]; + + foreach ($accounts as $account) { + if ($current > Carbon::now()) { + $row[] = null; + } else { + $row[] = $account->balance($current); + } + + } + + $chart->addRowArray($row); + $current->addDay(); + } + + $chart->generate(); + return Response::json($chart->getData()); + + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function allBudgetsHomeChart() + { + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Budget', 'string'); + $chart->addColumn('Budgeted', 'number'); + $chart->addColumn('Spent', 'number'); + + /** @var \FireflyIII\Database\Budget $bdt */ + $bdt = App::make('FireflyIII\Database\Budget'); + $budgets = $bdt->get(); + + /* + * Loop budgets: + */ + /** @var Budget $budget */ + foreach ($budgets as $budget) { + + /* + * Is there a repetition starting on this particular date? We can use that. + */ + /** @var \LimitRepetition $repetition */ + $repetition = $bdt->repetitionOnStartingOnDate($budget, Session::get('start')); + + /* + * If there is, use it. Otherwise, forget it. + */ + if (is_null($repetition)) { + // use the session start and end for our search query + $searchStart = Session::get('start'); + $searchEnd = Session::get('end'); + // the limit is zero: + $limit = 0; + + } else { + // use the limit's start and end for our search query + $searchStart = $repetition->startdate; + $searchEnd = $repetition->enddate; + // the limit is the repetitions limit: + $limit = floatval($repetition->amount); + } + + /* + * No matter the result of the search for the repetition, get all the transactions associated + * with the budget, and sum up the expenses made. + */ + $expenses = floatval($budget->transactionjournals()->before($searchEnd)->after($searchStart)->lessThan(0)->sum('amount')) * -1; + if ($expenses > 0) { + $chart->addRow($budget->name, $limit, $expenses); + } + } + + /* + * Finally, get all transactions WITHOUT a budget and add those as well. + * (yes this method is oddly specific). + */ + $noBudgetSet = $bdt->transactionsWithoutBudgetInDateRange(Session::get('start'), Session::get('end')); + $sum = $noBudgetSet->sum('amount') * -1; + $chart->addRow('No budget', 0, $sum); + + + $chart->generate(); + return Response::json($chart->getData()); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function allCategoriesHomeChart() + { + $data = []; + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Category', 'string'); + $chart->addColumn('Spent', 'number'); + + /** @var \FireflyIII\Database\TransactionJournal $tj */ + $tj = App::make('FireflyIII\Database\TransactionJournal'); + + /* + * Get the journals: + */ + $journals = $tj->getInDateRange(Session::get('start'), Session::get('end')); + + /** @var \TransactionJournal $journal */ + foreach ($journals as $journal) { + if ($journal->transactionType->type == 'Withdrawal') { + $amount = floatval($journal->transactions[1]->amount); + $amount = $amount < 0 ? $amount * -1 : $amount; + $category = $journal->categories()->first(); + if (!is_null($category)) { + if (isset($data[$category->name])) { + $data[$category->name] += $amount; + } else { + $data[$category->name] = $amount; + } + } + } + } + arsort($data); + foreach ($data as $key => $entry) { + $chart->addRow($key, $entry); + } + + + $chart->generate(); + return Response::json($chart->getData()); + + } + + public function recurringTransactionsOverview() + { + + /* + * Set of paid transaction journals. + * Set of unpaid recurring transactions. + */ + $paid = [ + 'items' => [], + 'amount' => 0 + ]; + $unpaid = [ + 'items' => [], + 'amount' => 0 + ]; + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Name', 'string'); + $chart->addColumn('Amount', 'number'); + + /** @var \FireflyIII\Database\Recurring $rcr */ + $rcr = App::make('FireflyIII\Database\Recurring'); + + /** @var \FireflyIII\Shared\Toolkit\Date $dateKit */ + $dateKit = App::make('FireflyIII\Shared\Toolkit\Date'); + + $recurring = $rcr->get(); + + /** @var \RecurringTransaction $entry */ + foreach ($recurring as $entry) { + /* + * Start another loop starting at the $date. + */ + $start = clone $entry->date; + $end = Carbon::now(); + + /* + * The jump we make depends on the $repeat_freq + */ + $current = clone $start; + + while ($current <= $end) { + /* + * Get end of period for $current: + */ + $currentEnd = clone $current; + $dateKit->endOfPeriod($currentEnd, $entry->repeat_freq); + + /* + * In the current session range? + */ + if (\Session::get('end') >= $current and $currentEnd >= \Session::get('start')) { + /* + * Lets see if we've already spent money on this recurring transaction (it hath recurred). + */ + /** @var TransactionJournal $set */ + $journal = $rcr->getJournalForRecurringInRange($entry, $current, $currentEnd); + + if (is_null($journal)) { + $unpaid['items'][] = $entry->name; + $unpaid['amount'] += (($entry->amount_max + $entry->amount_min) / 2); + } else { + $amount = floatval($journal->transactions[0]->amount); + $amount = $amount < 0 ? $amount * -1 : $amount; + $paid['items'][] = $journal->description; + $paid['amount'] += $amount; + } + } + + /* + * Add some time for the next loop! + */ + $dateKit->addPeriod($current, $entry->repeat_freq, intval($entry->skip)); + + } + + } + /** @var \RecurringTransaction $entry */ + $chart->addRow('Unpaid: ' . join(', ', $unpaid['items']), $unpaid['amount']); + $chart->addRow('Paid: ' . join(', ', $paid['items']), $paid['amount']); + + $chart->generate(); + return Response::json($chart->getData()); + + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Account.php b/app/lib/FireflyIII/Database/Account.php index 70eac86228..09bfa73dc0 100644 --- a/app/lib/FireflyIII/Database/Account.php +++ b/app/lib/FireflyIII/Database/Account.php @@ -16,6 +16,9 @@ class Account implements CUD, CommonDatabaseCalls, AccountInterface { use SwitchUser; + /** + * + */ public function __construct() { $this->setUser(\Auth::user()); @@ -423,4 +426,14 @@ class Account implements CUD, CommonDatabaseCalls, AccountInterface { // TODO: Implement findByWhat() method. } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + return $this->getUser()->accounts()->whereIn('id', $ids)->get(); + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Budget.php b/app/lib/FireflyIII/Database/Budget.php new file mode 100644 index 0000000000..aa7ef4c94c --- /dev/null +++ b/app/lib/FireflyIII/Database/Budget.php @@ -0,0 +1,155 @@ +setUser(\Auth::user()); + } + + /** + * @param \Budget $budget + * @param Carbon $date + * + * @return \LimitRepetition|null + */ + public function repetitionOnStartingOnDate(\Budget $budget, Carbon $date) + { + return \LimitRepetition:: + leftJoin('limits', 'limit_repetitions.limit_id', '=', 'limits.id')->leftJoin( + 'components', 'limits.component_id', '=', 'components.id' + )->where('limit_repetitions.startdate', $date->format('Y-m-d'))->where( + 'components.id', $budget->id + )->first(['limit_repetitions.*']); + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + // TODO: Implement validate() method. + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + return $this->getUser()->budgets()->get(); + } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } + + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function transactionsWithoutBudgetInDateRange(Carbon $start, Carbon $end) + { + // Add expenses that have no budget: + return \Auth::user()->transactionjournals()->whereNotIn( + 'transaction_journals.id', function ($query) use ($start, $end) { + $query->select('transaction_journals.id')->from('transaction_journals') + ->leftJoin( + 'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=', + 'transaction_journals.id' + ) + ->leftJoin('components', 'components.id', '=', 'component_transaction_journal.component_id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('components.class', 'Budget'); + } + )->before($end)->after($start)->lessThan(0)->transactionTypes(['Withdrawal'])->get(); + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/BudgetInterface.php b/app/lib/FireflyIII/Database/BudgetInterface.php new file mode 100644 index 0000000000..ac38f77e7b --- /dev/null +++ b/app/lib/FireflyIII/Database/BudgetInterface.php @@ -0,0 +1,31 @@ +setUser(\Auth::user()); + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + // TODO: Implement validate() method. + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/CategoryInterface.php b/app/lib/FireflyIII/Database/CategoryInterface.php new file mode 100644 index 0000000000..79dfcf343e --- /dev/null +++ b/app/lib/FireflyIII/Database/CategoryInterface.php @@ -0,0 +1,16 @@ +setUser(\Auth::user()); + } + + /** + * @param \RecurringTransaction $recurring + * @param Carbon $current + * @param Carbon $currentEnd + * + * @return \TransactionJournal|null + */ + public function getJournalForRecurringInRange(\RecurringTransaction $recurring, Carbon $start, Carbon $end) + { + return $this->getUser()->transactionjournals()->where('recurring_transaction_id', $recurring->id)->after($start)->before($end)->first(); + + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + // TODO: Implement validate() method. + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + return $this->getUser()->recurringtransactions()->get(); + } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/RecurringInterface.php b/app/lib/FireflyIII/Database/RecurringInterface.php new file mode 100644 index 0000000000..ed728ac9a0 --- /dev/null +++ b/app/lib/FireflyIII/Database/RecurringInterface.php @@ -0,0 +1,22 @@ +setUser(\Auth::user()); } + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getInDateRange(Carbon $start, Carbon $end) + { + return $this->getuser()->transactionjournals()->withRelevantData()->before($end)->after($start)->get(); + } + /** * @param Ardent $model @@ -250,4 +261,14 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData { // TODO: Implement findByWhat() method. } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionJournalInterface.php b/app/lib/FireflyIII/Database/TransactionJournalInterface.php index b791c06ad6..f08ed11891 100644 --- a/app/lib/FireflyIII/Database/TransactionJournalInterface.php +++ b/app/lib/FireflyIII/Database/TransactionJournalInterface.php @@ -7,6 +7,8 @@ */ namespace FireflyIII\Database; +use Carbon\Carbon; +use Illuminate\Support\Collection; /** * Interface TransactionJournalInterface @@ -15,5 +17,12 @@ namespace FireflyIII\Database; */ interface TransactionJournalInterface { + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getInDateRange(Carbon $start, Carbon $end); } \ No newline at end of file diff --git a/app/lib/FireflyIII/Shared/Preferences/Preferences.php b/app/lib/FireflyIII/Shared/Preferences/Preferences.php new file mode 100644 index 0000000000..0fa5bfda7a --- /dev/null +++ b/app/lib/FireflyIII/Shared/Preferences/Preferences.php @@ -0,0 +1,58 @@ +id)->where('name', $name)->first(); + if (is_null($default) && is_null($pref)) { + // return NULL + return null; + } + if (!is_null($pref)) { + return $pref; + } + if (!is_null($default) && is_null($pref)) { + // create preference, return that: + return $this->set($name, $default); + } + + return null; + + } + + /** + * @param $name + * @param $value + * + * @return \Preference + */ + public function set($name, $value) + { + $pref = \Preference::where('user_id', \Auth::user()->id)->where('name', $name)->first(); + if (is_null($pref)) { + $pref = new \Preference; + $pref->name = $name; + $pref->user()->associate(\Auth::user()); + + } + $pref->data = $value; + $pref->save(); + + + return $pref; + + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Shared/Preferences/PreferencesInterface.php b/app/lib/FireflyIII/Shared/Preferences/PreferencesInterface.php new file mode 100644 index 0000000000..4e7389695f --- /dev/null +++ b/app/lib/FireflyIII/Shared/Preferences/PreferencesInterface.php @@ -0,0 +1,29 @@ +addDay(); + break; + case 'weekly': + $currentEnd->addWeek()->subDay(); + break; + case 'monthly': + $currentEnd->addMonth()->subDay(); + break; + case 'quarterly': + $currentEnd->addMonths(3)->subDay(); + break; + case 'half-year': + $currentEnd->addMonths(6)->subDay(); + break; + case 'yearly': + $currentEnd->addYear()->subDay(); + break; + } + } + + /** + * @param Carbon $date + * @param $repeatFreq + * @param $skip + * + * @return Carbon + * @throws FireflyException + */ + public function addPeriod(Carbon $date, $repeatFreq, $skip) + { + $add = ($skip + 1); + switch ($repeatFreq) { + default: + throw new FireflyException('Cannot do addPeriod for $repeat_freq ' . $repeatFreq); + break; + case 'daily': + $date->addDays($add); + break; + case 'weekly': + $date->addWeeks($add); + break; + case 'monthly': + $date->addMonths($add); + break; + case 'quarterly': + $months = $add * 3; + $date->addMonths($months); + break; + case 'half-year': + $months = $add * 6; + $date->addMonths($months); + break; + case 'yearly': + $date->addYears($add); + break; + } + return $date; + } +} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 22d86fa854..9eced9703c 100644 --- a/app/routes.php +++ b/app/routes.php @@ -162,12 +162,15 @@ Route::group(['before' => 'auth'], function () { Route::get('/categories/delete/{category}',['uses' => 'CategoryController@delete','as' => 'categories.delete']); // chart controller - Route::get('/chart/home/account/{account?}', ['uses' => 'ChartController@homeAccount', 'as' => 'chart.home']); - Route::get('/chart/home/categories', ['uses' => 'ChartController@homeCategories', 'as' => 'chart.categories']); - Route::get('/chart/home/budgets', ['uses' => 'ChartController@homeBudgets', 'as' => 'chart.budgets']); + Route::get('/chart/home/account', ['uses' => 'GoogleChartController@allAccountsBalanceChart']); + Route::get('/chart/home/budgets', ['uses' => 'GoogleChartController@allBudgetsHomeChart']); + Route::get('/chart/home/categories', ['uses' => 'GoogleChartController@allCategoriesHomeChart']); + Route::get('/chart/home/recurring', ['uses' => 'GoogleChartController@recurringTransactionsOverview']); + + Route::get('/chart/home/info/{accountnameA}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']); Route::get('/chart/categories/show/{category}', ['uses' => 'ChartController@categoryShowChart','as' => 'chart.showcategory']); - Route::get('/chart/home/recurring', ['uses' => 'ChartController@homeRecurring', 'as' => 'chart.recurring']); + // (new charts for budgets) Route::get('/chart/budget/{budget}/default', ['uses' => 'ChartController@budgetDefault', 'as' => 'chart.budget.default']); Route::get('chart/budget/{budget}/no_envelope', ['uses' => 'ChartController@budgetNoLimits', 'as' => 'chart.budget.nolimit']); diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js index f99b5e0620..998514d0f1 100644 --- a/public/assets/javascript/firefly/gcharts.js +++ b/public/assets/javascript/firefly/gcharts.js @@ -1,8 +1,5 @@ google.load('visualization', '1.0', {'packages': ['corechart']}); -/* - If this method has not been defined (yet) it will error out. - */ function googleLineChart(URL, container) { $.getJSON(URL).success(function (data) { /* @@ -10,16 +7,114 @@ function googleLineChart(URL, container) { */ gdata = new google.visualization.DataTable(data); + /* + Format as money + */ + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + for (i = 1; i < gdata.getNumberOfColumns(); i++) { + money.format(gdata, i); + } + /* Create a new google charts object. */ var chart = new google.visualization.LineChart(document.getElementById(container)); /* - Draw it: + Draw it: */ chart.draw(gdata, defaultLineChartOptions); + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googleBarChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Format as money + */ + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + for (i = 1; i < gdata.getNumberOfColumns(); i++) { + money.format(gdata, i); + } + + /* + Create a new google charts object. + */ + var chart = new google.visualization.BarChart(document.getElementById(container)); + + /* + Draw it: + */ + chart.draw(gdata, defaultBarChartOptions); + + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googleColumnChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Format as money + */ + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + for (i = 1; i < gdata.getNumberOfColumns(); i++) { + money.format(gdata, i); + } + + /* + Create a new google charts object. + */ + var chart = new google.visualization.ColumnChart(document.getElementById(container)); + + /* + Draw it: + */ + chart.draw(gdata, defaultColumnChartOptions); + + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googlePieChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Format as money + */ + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + for (i = 1; i < gdata.getNumberOfColumns(); i++) { + money.format(gdata, i); + } + + /* + Create a new google charts object. + */ + var chart = new google.visualization.PieChart(document.getElementById(container)); + + /* + Draw it: + */ + chart.draw(gdata, defaultPieChartOptions); + }).fail(function () { $('#' + container).addClass('google-chart-error'); }); diff --git a/public/assets/javascript/firefly/gcharts.options.js b/public/assets/javascript/firefly/gcharts.options.js index 594d1e26ad..a038b286a8 100644 --- a/public/assets/javascript/firefly/gcharts.options.js +++ b/public/assets/javascript/firefly/gcharts.options.js @@ -1,3 +1,59 @@ var defaultLineChartOptions = { + curveType: 'function', + legend: { + position: 'none' + }, + lineWidth: 1, + chartArea: { + left: 50, + top: 10, + width: '85%', + height: '80%' + }, + height: 400, + vAxis: {format: '\u20AC #'} + +}; + +var defaultBarChartOptions = { + height: 400, + hAxis: {format: '\u20AC #'}, + chartArea: { + left: 75, + top: 10, + width: '100%', + height: '90%' + }, + + legend: { + position: 'none' + } +}; + +var defaultColumnChartOptions = { + height: 400, + chartArea: { + left: 50, + top: 10, + width: '85%', + height: '80%' + }, + vAxis: {format: '\u20AC #'}, + legend: { + position: 'none' + } +}; + +var defaultPieChartOptions = { + chartArea: { + left: 0, + top: 0, + width: '100%', + height: '100%' + }, + height:200, + legend: { + position: 'none' + } }; \ No newline at end of file diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index 834dc8db66..49e6508c67 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -7,6 +7,9 @@ google.setOnLoadCallback(drawChart); function drawChart() { console.log(1); googleLineChart('chart/home/account', 'accounts-chart'); + googleBarChart('chart/home/budgets','budgets-chart'); + googleColumnChart('chart/home/categories','categories-chart'); + googlePieChart('chart/home/recurring','recurring-chart') } From 0707603b63ba180df006d9207285140a3bb8d154 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 29 Oct 2014 12:26:26 +0100 Subject: [PATCH 23/57] Add buttons and a placeholder. --- app/views/accounts/show.blade.php | 2 +- app/views/piggybanks/index.blade.php | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index 195225ed9a..21993d5d85 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -7,7 +7,7 @@ {{{$account->name}}}
    -
    +
    diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 23bd539030..5d905b2967 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -25,6 +25,12 @@
    + @if($accounts[$piggyBank->account_id]['account']->leftOnAccount > 0) + + @endif + @if($piggyBank->currentRelevantRep()->currentamount > 0) + + @endif
    @@ -176,14 +182,7 @@ - - --}}
    @@ -204,4 +203,15 @@
    + + + + + @stop From ad479a5c7f15dbc836913c90ccadcb4bc2667a91 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 29 Oct 2014 12:33:19 +0100 Subject: [PATCH 24/57] Added some placeholders. --- app/views/accounts/show.blade.php | 4 +--- app/views/budgets/show.blade.php | 3 +-- app/views/categories/show.blade.php | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index 195225ed9a..8cac788611 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -7,7 +7,7 @@ {{{$account->name}}}
    -
    +
    @@ -161,7 +161,6 @@ @stop @section('styles') -{{HTML::style('assets/stylesheets/highslide/highslide.css')}} {{HTML::style('assets/stylesheets/datatables/dataTables.bootstrap.css')}} @stop @@ -171,6 +170,5 @@ {{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} {{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} -{{HTML::script('assets/javascript/highcharts/highcharts.js')}} {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file diff --git a/app/views/budgets/show.blade.php b/app/views/budgets/show.blade.php index 0d2f43f3d2..bc3de1a0ca 100644 --- a/app/views/budgets/show.blade.php +++ b/app/views/budgets/show.blade.php @@ -40,7 +40,7 @@
    -
    +
    @if($view == 1)
    @endif @@ -101,7 +101,6 @@ @stop @section('scripts') -{{HTML::script('assets/javascript/highcharts/highcharts.js')}} @if($view == 1) {{HTML::script('assets/javascript/firefly/budgets/limit.js')}} @endif diff --git a/app/views/categories/show.blade.php b/app/views/categories/show.blade.php index 1d7d549d0b..f6f4678b6d 100644 --- a/app/views/categories/show.blade.php +++ b/app/views/categories/show.blade.php @@ -21,7 +21,7 @@ @include('partials.date_nav')
    -

    (Some chart here)

    +
    @@ -40,6 +40,5 @@ -{{HTML::script('assets/javascript/highcharts/highcharts.js')}} {{HTML::script('assets/javascript/firefly/categories.js')}} @stop \ No newline at end of file From d5e1da594820ed4cbc4d91a45f1e07e2ead0ed6c Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 29 Oct 2014 12:39:15 +0100 Subject: [PATCH 25/57] Some more stats on the piggy banks. --- app/controllers/PiggybankController.php | 36 +++++++++++++++---------- app/views/piggybanks/index.blade.php | 8 +++++- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 6f08e87c1f..71f4e572b3 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -62,7 +62,7 @@ class PiggybankController extends BaseController View::share('mainTitleIcon', 'fa-sort-amount-asc'); return View::make('piggybanks.create-piggybank')->with('accounts', $accounts) - ->with('periods', $periods); + ->with('periods', $periods); } /** @@ -148,14 +148,14 @@ class PiggybankController extends BaseController View::share('mainTitleIcon', 'fa-rotate-left'); return View::make('piggybanks.edit-repeated')->with('piggybank', $piggyBank)->with('accounts', $accounts) - ->with('periods', $periods); + ->with('periods', $periods); } else { // piggy bank. View::share('title', 'Piggy banks'); View::share('mainTitleIcon', 'fa-sort-amount-asc'); return View::make('piggybanks.edit-piggybank')->with('piggybank', $piggyBank)->with('accounts', $accounts) - ->with('periods', $periods); + ->with('periods', $periods); } @@ -196,11 +196,11 @@ class PiggybankController extends BaseController } break; } - if($piggyBank->repeats == 1) { - $route = 'piggybanks.index.repeated'; + if ($piggyBank->repeats == 1) { + $route = 'piggybanks.index.repeated'; } else { - $route = 'piggybanks.index.piggybanks'; + $route = 'piggybanks.index.piggybanks'; } return Redirect::route($route); } @@ -225,7 +225,15 @@ class PiggybankController extends BaseController $id = $account->id; if (!isset($accounts[$id])) { $account->leftOnAccount = $this->_repository->leftOnAccount($account); - $accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)]; + $accounts[$id] = [ + 'account' => $account, + 'left' => $this->_repository->leftOnAccount($account), + 'tosave' => $piggybank->targetamount, + 'saved' => $piggybank->currentRelevantRep()->currentamount + ]; + } else { + $accounts[$id]['tosave'] += $piggybank->targetamount; + $accounts[$id]['saved'] += $piggybank->currentRelevantRep()->currentamount; } } @@ -234,9 +242,9 @@ class PiggybankController extends BaseController View::share('mainTitleIcon', 'fa-sort-amount-asc'); return View::make('piggybanks.index')->with('piggybanks', $piggybanks) - ->with('countRepeating', $countRepeating) - ->with('countNonRepeating', $countNonRepeating) - ->with('accounts', $accounts); + ->with('countRepeating', $countRepeating) + ->with('countNonRepeating', $countNonRepeating) + ->with('accounts', $accounts); } /** @@ -285,9 +293,9 @@ class PiggybankController extends BaseController return View::make('piggybanks.index')->with('piggybanks', $piggybanks) - ->with('countRepeating', $countRepeating) - ->with('countNonRepeating', $countNonRepeating) - ->with('accounts', $accounts); + ->with('countRepeating', $countRepeating) + ->with('countNonRepeating', $countNonRepeating) + ->with('accounts', $accounts); } /** @@ -311,7 +319,7 @@ class PiggybankController extends BaseController } return View::make('piggybanks.show')->with('piggyBank', $piggyBank)->with('leftOnAccount', $leftOnAccount) - ->with('balance', $balance); + ->with('balance', $balance); } /** diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 5d905b2967..981d7bc1a2 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -186,17 +186,23 @@ --}}
    -
    +

    Account information

    {{mf($piggyBank->currentRelevantRep()->currentamount)}}
    + + + @foreach($accounts as $account) + + + @endforeach
    Account Left for piggy banksTotal planned savingsSaved so farLeft to save
    {{{$account['account']->name}}} {{mf($account['left'])}}{{mf($account['tosave'])}}{{mf($account['saved'])}}{{mf($account['tosave']-$account['saved'])}}
    From ffcd1fde0f2af1fb031ee307ee871f7e543712e1 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 30 Oct 2014 18:06:29 +0100 Subject: [PATCH 26/57] Even more charts and tables. --- app/controllers/GoogleChartController.php | 158 ++++++++++++++++++ app/controllers/GoogleTableController.php | 110 ++++++++++++ app/routes.php | 10 +- app/views/accounts/show.blade.php | 27 ++- public/assets/javascript/firefly/accounts.js | 20 ++- public/assets/javascript/firefly/gcharts.js | 116 ++++++++++++- .../javascript/firefly/gcharts.options.js | 9 +- 7 files changed, 428 insertions(+), 22 deletions(-) create mode 100644 app/controllers/GoogleTableController.php diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index c4e5ab89d8..8d08b8d3e7 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -174,6 +174,10 @@ class GoogleChartController extends BaseController } + /** + * @return \Illuminate\Http\JsonResponse + * @throws \Firefly\Exception\FireflyException + */ public function recurringTransactionsOverview() { @@ -260,4 +264,158 @@ class GoogleChartController extends BaseController return Response::json($chart->getData()); } + + /** + * @param Account $account + */ + public function accountBalanceChart(Account $account) + { + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Day of month', 'date'); + $chart->addColumn('Balance for ' . $account->name, 'number'); + + /* + * Loop the date, then loop the accounts, then add balance. + */ + $start = Session::get('start'); + $end = Session::get('end'); + $current = clone $start; + + while ($end >= $current) { + $row = [clone $current]; + if ($current > Carbon::now()) { + $row[] = null; + } else { + $row[] = $account->balance($current); + } + + $chart->addRowArray($row); + $current->addDay(); + } + + + $chart->generate(); + return Response::json($chart->getData()); + } + + /** + * @param Account $account + * + * @return \Illuminate\Http\JsonResponse + */ + public function accountSankeyOutChart(Account $account) + { + // collect all relevant entries. + $set = []; + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('From', 'string'); + $chart->addColumn('To', 'string', 'domain'); + $chart->addColumn('Weight', 'number'); + + $transactions = $account->transactions()->with( + ['transactionjournal', 'transactionjournal.transactions', 'transactionjournal.budgets', 'transactionjournal.transactiontype', + 'transactionjournal.categories'] + )->before(Session::get('end'))->after( + Session::get('start') + )->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $amount = floatval($transaction->amount); + $type = $transaction->transactionJournal->transactionType->type; + + if ($amount < 0 && $type != 'Transfer') { + + // from account to a budget (if present). + $budgetName = isset($transaction->transactionJournal->budgets[0]) ? $transaction->transactionJournal->budgets[0]->name : '(no budget)'; + $set[] = [$account->name, $budgetName, $amount * -1]; + + // from budget to category. + $categoryName = isset($transaction->transactionJournal->categories[0]) ? ' ' . $transaction->transactionJournal->categories[0]->name + : '(no cat)'; + $set[] = [$budgetName, $categoryName, $amount * -1]; + } + } + // loop the set, group everything together: + $grouped = []; + foreach ($set as $entry) { + $key = $entry[0] . $entry[1]; + if (isset($grouped[$key])) { + $grouped[$key][2] += $entry[2]; + } else { + $grouped[$key] = $entry; + } + } + + // add rows to the chart: + foreach ($grouped as $entry) { + $chart->addRow($entry[0], $entry[1], $entry[2]); + } + + $chart->generate(); + return Response::json($chart->getData()); + + } + + /** + * @param Account $account + * + * @return \Illuminate\Http\JsonResponse + */ + public function accountSankeyInChart(Account $account) + { + // collect all relevant entries. + $set = []; + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('From', 'string'); + $chart->addColumn('To', 'string', 'domain'); + $chart->addColumn('Weight', 'number'); + + $transactions = $account->transactions()->with( + ['transactionjournal', 'transactionjournal.transactions' => function ($q) { + $q->where('amount', '<', 0); + }, 'transactionjournal.budgets', 'transactionjournal.transactiontype', 'transactionjournal.categories'] + )->before(Session::get('end'))->after( + Session::get('start') + )->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $amount = floatval($transaction->amount); + $type = $transaction->transactionJournal->transactionType->type; + + if ($amount > 0 && $type != 'Transfer') { + + $otherAccount = $transaction->transactionJournal->transactions[0]->account->name; + $categoryName = isset($transaction->transactionJournal->categories[0]) ? $transaction->transactionJournal->categories[0]->name + : '(no cat)'; + $set[] = [$otherAccount, $categoryName, $amount]; + $set[] = [$categoryName, $account->name, $amount]; + } + } + // loop the set, group everything together: + $grouped = []; + foreach ($set as $entry) { + $key = $entry[0] . $entry[1]; + if (isset($grouped[$key])) { + $grouped[$key][2] += $entry[2]; + } else { + $grouped[$key] = $entry; + } + } + + // add rows to the chart: + foreach ($grouped as $entry) { + $chart->addRow($entry[0], $entry[1], $entry[2]); + } + + $chart->generate(); + return Response::json($chart->getData()); + + } } \ No newline at end of file diff --git a/app/controllers/GoogleTableController.php b/app/controllers/GoogleTableController.php new file mode 100644 index 0000000000..727b6af6fb --- /dev/null +++ b/app/controllers/GoogleTableController.php @@ -0,0 +1,110 @@ +addColumn('ID', 'number'); + $chart->addColumn('ID_Edit', 'string'); + $chart->addColumn('ID_Delete', 'string'); + $chart->addColumn('Date', 'date'); + $chart->addColumn('Description_URL', 'string'); + $chart->addColumn('Description', 'string'); + $chart->addColumn('Amount', 'number'); + $chart->addColumn('From_URL', 'string'); + $chart->addColumn('From', 'string'); + $chart->addColumn('To_URL', 'string'); + $chart->addColumn('To', 'string'); + $chart->addColumn('Budget_URL', 'string'); + $chart->addColumn('Budget', 'string'); + $chart->addColumn('Category_URL', 'string'); + $chart->addColumn('Category', 'string'); + + /* + * Find transactions: + */ + $accountID = $account->id; + $transactions = $account->transactions()->with( + ['transactionjournal', 'transactionjournal.transactions' => function ($q) use ($accountID) { + $q->where('account_id', '!=', $accountID); + }, 'transactionjournal.budgets', 'transactionjournal.transactiontype', + 'transactionjournal.categories'] + )->before(Session::get('end'))->after( + Session::get('start') + )->orderBy('date', 'DESC')->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $date = $transaction->transactionJournal->date; + $descriptionURL = route('transactions.show', $transaction->transaction_journal_id); + $description = $transaction->transactionJournal->description; + $amount = floatval($transaction->amount); + + if ($transaction->transactionJournal->transactions[0]->account->id == $account->id) { + $opposingAccountURI = route('accounts.show', $transaction->transactionJournal->transactions[1]->account->id); + $opposingAccountName = $transaction->transactionJournal->transactions[1]->account->name; + } else { + $opposingAccountURI = route('accounts.show', $transaction->transactionJournal->transactions[0]->account->id); + $opposingAccountName = $transaction->transactionJournal->transactions[0]->account->name; + } + if (isset($transaction->transactionJournal->budgets[0])) { + $budgetURL = route('budgets.show', $transaction->transactionJournal->budgets[0]->id); + $budget = $transaction->transactionJournal->budgets[0]->name; + } else { + $budgetURL = ''; + $budget = ''; + } + + if (isset($transaction->transactionJournal->categories[0])) { + $categoryURL = route('categories.show', $transaction->transactionJournal->categories[0]->id); + $category = $transaction->transactionJournal->categories[0]->name; + } else { + $budgetURL = ''; + $budget = ''; + } + + + if ($amount < 0) { + $from = $account->name; + $fromURL = route('accounts.show', $account->id); + + $to = $opposingAccountName; + $toURL = $opposingAccountURI; + } else { + $to = $account->name; + $toURL = route('accounts.show', $account->id); + + $from = $opposingAccountName; + $fromURL = $opposingAccountURI; + } + + $budcat = 'Budcat'; + $id = $transaction->transactionJournal->id; + $edit = route('transactions.edit', $transaction->transactionJournal->id); + $delete = route('transactions.delete', $transaction->transactionJournal->id); + $chart->addRow( + $id, $edit, $delete, $date, $descriptionURL, $description, $amount, $fromURL, $from, $toURL, $to, $budgetURL, $budget, $categoryURL, $category + ); + } + +// Date +// Description +// Amount (€) +// From +// To +// Budget / category +// ID + + + $chart->generate(); + return Response::json($chart->getData()); + } +} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 9eced9703c..049619b7e3 100644 --- a/app/routes.php +++ b/app/routes.php @@ -161,11 +161,19 @@ Route::group(['before' => 'auth'], function () { Route::get('/categories/edit/{category}',['uses' => 'CategoryController@edit','as' => 'categories.edit']); Route::get('/categories/delete/{category}',['uses' => 'CategoryController@delete','as' => 'categories.delete']); - // chart controller + // google chart controller Route::get('/chart/home/account', ['uses' => 'GoogleChartController@allAccountsBalanceChart']); Route::get('/chart/home/budgets', ['uses' => 'GoogleChartController@allBudgetsHomeChart']); Route::get('/chart/home/categories', ['uses' => 'GoogleChartController@allCategoriesHomeChart']); Route::get('/chart/home/recurring', ['uses' => 'GoogleChartController@recurringTransactionsOverview']); + Route::get('/chart/account/{account}', ['uses' => 'GoogleChartController@accountBalanceChart']); + Route::get('/chart/sankey/{account}/out', ['uses' => 'GoogleChartController@accountSankeyOutChart']); + Route::get('/chart/sankey/{account}/in', ['uses' => 'GoogleChartController@accountSankeyInChart']); + + // google table controller + Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); + + Route::get('/chart/home/info/{accountnameA}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']); diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index 8cac788611..1985b8c2c7 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -7,7 +7,7 @@ {{{$account->name}}}
    -
    +
    @@ -71,7 +71,7 @@ Out
    -
    +
    @@ -81,7 +81,7 @@ In
    -
    +
    @@ -94,20 +94,8 @@ Transactions
    +
    - - - - - - - - - - - - -
    DateDescriptionAmount (€)FromToBudget / categoryID
    {{-- @@ -170,5 +158,12 @@ {{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} {{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} + + + + +{{HTML::script('assets/javascript/firefly/gcharts.options.js')}} +{{HTML::script('assets/javascript/firefly/gcharts.js')}} + {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file diff --git a/public/assets/javascript/firefly/accounts.js b/public/assets/javascript/firefly/accounts.js index 33d0b22040..702335e3ae 100644 --- a/public/assets/javascript/firefly/accounts.js +++ b/public/assets/javascript/firefly/accounts.js @@ -1,10 +1,24 @@ $(function () { + + if (typeof(googleLineChart) == "function") { + googleLineChart('chart/account/' + accountID, 'overview-chart'); + } + // + if(typeof(googleSankeyChart) == 'function') { + googleSankeyChart('chart/sankey/' + accountID + '/out','account-out-sankey'); + googleSankeyChart('chart/sankey/' + accountID + '/in','account-in-sankey'); + } + if(typeof(googleTable) == 'function') { + googleTable('table/account/' + accountID + '/transactions','account-transactions'); + } + + if ($('#accountTable').length == 1) { drawDatatable(); } - if ($('#overviewChart').length == 1) { - drawOverviewChart(); - } + //if ($('#overviewChart').length == 1) { + // drawOverviewChart(); + //} }); diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js index 998514d0f1..32c748b0ac 100644 --- a/public/assets/javascript/firefly/gcharts.js +++ b/public/assets/javascript/firefly/gcharts.js @@ -1,4 +1,4 @@ -google.load('visualization', '1.0', {'packages': ['corechart']}); +google.load('visualization', '1.1', {'packages': ['corechart', 'sankey', 'table']}); function googleLineChart(URL, container) { $.getJSON(URL).success(function (data) { @@ -115,6 +115,120 @@ function googlePieChart(URL, container) { */ chart.draw(gdata, defaultPieChartOptions); + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googleSankeyChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Format as money + */ + + console.log(gdata.getNumberOfRows()) + if (gdata.getNumberOfRows() < 1) { + console.log('remove'); + $('#' + container).parent().parent().remove(); + return; + } else if (gdata.getNumberOfRows() < 6) { + defaultSankeyChartOptions.height = 100 + } else { + defaultSankeyChartOptions.height = 400 + } + + + /* + Create a new google charts object. + */ + var chart = new google.visualization.Sankey(document.getElementById(container)); + + /* + Draw it: + */ + chart.draw(gdata, defaultSankeyChartOptions); + + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + +function googleTable(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + var gdata = new google.visualization.DataTable(data); + + /* + Create a new google charts object. + */ + var chart = new google.visualization.Table(document.getElementById(container)); + + /* + Do something with formatters: + */ + var x = gdata.getNumberOfColumns(); + var columnsToHide = new Array; + var URLFormatter = new google.visualization.PatternFormat('{1}'); + + var EditButtonFormatter = new google.visualization.PatternFormat('
    '); + + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + + + for (var i = 0; i < x; i++) { + var label = gdata.getColumnLabel(i); + console.log('Column ' + i + ':' + label); + /* + Format a string using the previous column as URL. + */ + if (label == 'Description' || label == 'From' || label == 'To' || label == 'Budget' || label == 'Category') { + URLFormatter.format(gdata, [i - 1, i], i); + columnsToHide.push(i - 1); + } + if(label == 'ID') { + EditButtonFormatter.format(gdata, [i+1,i+2],i); + columnsToHide.push(i+1,i+2); + } + + /* + Format with buttons: + */ + + + /* + Format as money + */ + if (label == 'Amount') { + money.format(gdata, i); + } + + } + + + //var formatter = new google.visualization.PatternFormat('{1}'); + + //formatter.format(gdata, [5, 6], 6); + //formatter.format(gdata, [7, 8], 8); + + + var view = new google.visualization.DataView(gdata); + // hide certain columns: + + view.hideColumns(columnsToHide); + + + /* + Draw it: + */ + chart.draw(view, defaultTableOptions); + }).fail(function () { $('#' + container).addClass('google-chart-error'); }); diff --git a/public/assets/javascript/firefly/gcharts.options.js b/public/assets/javascript/firefly/gcharts.options.js index a038b286a8..75064974fc 100644 --- a/public/assets/javascript/firefly/gcharts.options.js +++ b/public/assets/javascript/firefly/gcharts.options.js @@ -52,8 +52,15 @@ var defaultPieChartOptions = { width: '100%', height: '100%' }, - height:200, + height: 200, legend: { position: 'none' } +}; + +var defaultSankeyChartOptions = { + height: 400 +} +var defaultTableOptions = { + allowHtml: true }; \ No newline at end of file From dd9f08d4fa3c40aee2e773f2b01c9d54cdea7725 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 30 Oct 2014 18:12:27 +0100 Subject: [PATCH 27/57] New code for charts. --- app/views/accounts/show.blade.php | 69 +++++-------------------------- 1 file changed, 11 insertions(+), 58 deletions(-) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index 21993d5d85..db15fc88da 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -7,7 +7,7 @@ {{{$account->name}}}
    -
    +
    @@ -71,7 +71,7 @@ Out
    -
    +
    @@ -81,7 +81,7 @@ In
    -
    +
    @@ -94,57 +94,8 @@ Transactions
    - -
    - - - - - - - - - - - -
    DateDescriptionAmount (€)FromToBudget / categoryID
    - - {{-- - - @if(count($show['statistics']['accounts']) > 0) - - - - - @endif - @if(isset($show['statistics']['Category']) && count($show['statistics']['Category']) > 0) - - - - - @endif - @if(isset($show['statistics']['Budget']) && count($show['statistics']['Budget']) > 0) - - - - - @endif -
    Related accounts - @foreach($show['statistics']['accounts'] as $acct) - {{{$acct->name}}} - @endforeach -
    Related categories - @foreach($show['statistics']['Category'] as $cat) - {{{$cat->name}}} - @endforeach -
    Related budgets - @foreach($show['statistics']['Budget'] as $bud) - {{{$bud->name}}} - @endforeach -
    - --}} +
    - @@ -161,16 +112,18 @@ @stop @section('styles') -{{HTML::style('assets/stylesheets/highslide/highslide.css')}} -{{HTML::style('assets/stylesheets/datatables/dataTables.bootstrap.css')}} @stop @section('scripts') -{{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} -{{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} -{{HTML::script('assets/javascript/highcharts/highcharts.js')}} + + +{{HTML::script('assets/javascript/firefly/gcharts.options.js')}} +{{HTML::script('assets/javascript/firefly/gcharts.js')}} + + + {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file From f0c0002a6ddcb50b1912ec3e58293678b52e084e Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 30 Oct 2014 18:24:10 +0100 Subject: [PATCH 28/57] Some cleanup --- app/views/accounts/show.blade.php | 61 ------------------------------- 1 file changed, 61 deletions(-) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index db15fc88da..8d5842fd67 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -14,52 +14,6 @@
    @include('partials.date_nav') - - -
    -
    - Summary -
    -
    - On the todo list. - {{-- - - - - - - - - - - - - - - - - - - - - - -
    Expense / incomeTransfers
    Out - {{mf($show['statistics']['period']['out'])}} - - - {{mf($show['statistics']['period']['t_out'])}} - -
    In - {{mf($show['statistics']['period']['in'])}} - - - {{mf($show['statistics']['period']['t_in'])}} - -
    Difference{{mf($show['statistics']['period']['diff'])}}{{mf($show['statistics']['period']['t_diff'])}}
    - --}} -
    -
    @@ -101,19 +55,7 @@ -{{-- -
    -
    -

    Transactions For selected account and period

    - @include('paginated.transactions',['journals' => $show['journals'],'sum' => true]) -
    -
    ---}} @stop - -@section('styles') -@stop - @section('scripts') {{HTML::script('assets/javascript/firefly/gcharts.options.js')}} {{HTML::script('assets/javascript/firefly/gcharts.js')}} - - - {{HTML::script('assets/javascript/firefly/accounts.js')}} @stop \ No newline at end of file From 2d5b0d0f99b066c9645e73cc6a83f2881f82c085 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 30 Oct 2014 19:26:28 +0100 Subject: [PATCH 29/57] Moved some stuff around. --- app/lib/Firefly/Helper/Controllers/Json.php | 7 ------- app/lib/FireflyIII/Database/Account.php | 3 +++ app/lib/FireflyIII/Database/AccountType.php | 20 +++++++++++++------ app/lib/FireflyIII/Database/Budget.php | 4 +++- app/lib/FireflyIII/Database/Category.php | 3 +++ .../{ => Ifaces}/AccountInterface.php | 2 +- .../{ => Ifaces}/AccountTypeInterface.php | 9 +-------- .../Database/{ => Ifaces}/BudgetInterface.php | 2 +- .../FireflyIII/Database/{ => Ifaces}/CUD.php | 2 +- .../{ => Ifaces}/CategoryInterface.php | 2 +- .../{ => Ifaces}/CommonDatabaseCalls.php | 8 +------- .../{ => Ifaces}/RecurringInterface.php | 2 +- .../TransactionCurrencyInterface.php | 8 +------- .../{ => Ifaces}/TransactionInterface.php | 8 +------- .../TransactionJournalInterface.php | 8 +------- .../{ => Ifaces}/TransactionTypeInterface.php | 8 +------- app/lib/FireflyIII/Database/Recurring.php | 3 +++ app/lib/FireflyIII/Database/SwitchUser.php | 7 +------ app/lib/FireflyIII/Database/Transaction.php | 10 +++------- .../Database/TransactionCurrency.php | 19 ++++++++++++------ .../Database/TransactionJournal.php | 10 +++------- .../FireflyIII/Database/TransactionType.php | 19 ++++++++++++------ .../Exception/NotImplementedException.php | 7 ------- app/lib/FireflyIII/Shared/Json/Account.php | 12 +++++------ 24 files changed, 75 insertions(+), 108 deletions(-) rename app/lib/FireflyIII/Database/{ => Ifaces}/AccountInterface.php (98%) rename app/lib/FireflyIII/Database/{ => Ifaces}/AccountTypeInterface.php (51%) rename app/lib/FireflyIII/Database/{ => Ifaces}/BudgetInterface.php (93%) rename app/lib/FireflyIII/Database/{ => Ifaces}/CUD.php (95%) rename app/lib/FireflyIII/Database/{ => Ifaces}/CategoryInterface.php (81%) rename app/lib/FireflyIII/Database/{ => Ifaces}/CommonDatabaseCalls.php (87%) rename app/lib/FireflyIII/Database/{ => Ifaces}/RecurringInterface.php (92%) rename app/lib/FireflyIII/Database/{ => Ifaces}/TransactionCurrencyInterface.php (70%) rename app/lib/FireflyIII/Database/{ => Ifaces}/TransactionInterface.php (52%) rename app/lib/FireflyIII/Database/{ => Ifaces}/TransactionJournalInterface.php (76%) rename app/lib/FireflyIII/Database/{ => Ifaces}/TransactionTypeInterface.php (53%) diff --git a/app/lib/Firefly/Helper/Controllers/Json.php b/app/lib/Firefly/Helper/Controllers/Json.php index 80b87286f7..34e4f1f11f 100644 --- a/app/lib/Firefly/Helper/Controllers/Json.php +++ b/app/lib/Firefly/Helper/Controllers/Json.php @@ -1,11 +1,4 @@ first(); } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal.php index 690ddf5697..a0dcacff8c 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal.php @@ -1,10 +1,4 @@ Date: Thu, 30 Oct 2014 19:26:43 +0100 Subject: [PATCH 30/57] Clean up the routes. --- app/routes.php | 345 ++++++++++++++++++++++++------------------------- 1 file changed, 172 insertions(+), 173 deletions(-) diff --git a/app/routes.php b/app/routes.php index 049619b7e3..3c10e4b770 100644 --- a/app/routes.php +++ b/app/routes.php @@ -3,163 +3,157 @@ //use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - // models: -Route::bind('account', function($value, $route) - { - if(Auth::check()) { +Route::bind( + 'account', function ($value, $route) { + if (Auth::check()) { $account = Account:: - leftJoin('account_types','account_types.id','=','accounts.account_type_id')-> - where('account_types.editable',1)-> - where('accounts.id', $value)-> - where('user_id',Auth::user()->id)-> - first(['accounts.*']); - if($account) { + leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')->where('account_types.editable', 1)->where('accounts.id', $value) + ->where('user_id', Auth::user()->id)->first(['accounts.*']); + if ($account) { return $account; } } App::abort(404); - }); - -Route::bind('accountname', function($value, $route) - { - if(Auth::check()) { - return Account:: - leftJoin('account_types','account_types.id','=','accounts.account_type_id')-> - where('account_types.editable',1)-> - where('name', $value)-> - where('user_id',Auth::user()->id)->first(); - } - return null; - }); - - -Route::bind('recurring', function($value, $route) - { - if(Auth::check()) { - return RecurringTransaction:: - where('id', $value)-> - where('user_id',Auth::user()->id)->first(); - } - return null; - }); -Route::bind('budget', function($value, $route) - { - if(Auth::check()) { - return Budget:: - where('id', $value)-> - where('user_id',Auth::user()->id)->first(); - } - return null; - }); - -Route::bind('reminder', function($value, $route) - { - if(Auth::check()) { - return Reminder:: - where('id', $value)-> - where('user_id',Auth::user()->id)->first(); - } - return null; - }); - -Route::bind('category', function($value, $route) -{ - if(Auth::check()) { - return Category:: - where('id', $value)-> - where('user_id',Auth::user()->id)->first(); } - return null; -}); +); -Route::bind('tj', function($value, $route) - { - if(Auth::check()) { +Route::bind( + 'accountname', function ($value, $route) { + if (Auth::check()) { + return Account:: + leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')->where('account_types.editable', 1)->where('name', $value)->where( + 'user_id', Auth::user()->id + )->first(); + } + return null; + } +); + + +Route::bind( + 'recurring', function ($value, $route) { + if (Auth::check()) { + return RecurringTransaction:: + where('id', $value)->where('user_id', Auth::user()->id)->first(); + } + return null; + } +); +Route::bind( + 'budget', function ($value, $route) { + if (Auth::check()) { + return Budget:: + where('id', $value)->where('user_id', Auth::user()->id)->first(); + } + return null; + } +); + +Route::bind( + 'reminder', function ($value, $route) { + if (Auth::check()) { + return Reminder:: + where('id', $value)->where('user_id', Auth::user()->id)->first(); + } + return null; + } +); + +Route::bind( + 'category', function ($value, $route) { + if (Auth::check()) { + return Category:: + where('id', $value)->where('user_id', Auth::user()->id)->first(); + } + return null; + } +); + +Route::bind( + 'tj', function ($value, $route) { + if (Auth::check()) { return TransactionJournal:: - where('id', $value)-> - where('user_id',Auth::user()->id)->first(); + where('id', $value)->where('user_id', Auth::user()->id)->first(); } return null; - }); + } +); -Route::bind('limit', function($value, $route) - { - if(Auth::check()) { +Route::bind( + 'limit', function ($value, $route) { + if (Auth::check()) { return Limit:: - where('limits.id', $value)-> - leftJoin('components','components.id','=','limits.component_id')-> - where('components.class','Budget')-> - where('components.user_id',Auth::user()->id)->first(['limits.*']); + where('limits.id', $value)->leftJoin('components', 'components.id', '=', 'limits.component_id')->where('components.class', 'Budget')->where( + 'components.user_id', Auth::user()->id + )->first(['limits.*']); } return null; - }); + } +); -Route::bind('limitrepetition', function($value, $route) - { - if(Auth::check()) { +Route::bind( + 'limitrepetition', function ($value, $route) { + if (Auth::check()) { return LimitRepetition:: - where('limit_repetitions.id', $value)-> - leftjoin('limits','limits.id','=','limit_repetitions.limit_id')-> - leftJoin('components','components.id','=','limits.component_id')-> - where('components.class','Budget')-> - where('components.user_id',Auth::user()->id)->first(['limit_repetitions.*']); + where('limit_repetitions.id', $value)->leftjoin('limits', 'limits.id', '=', 'limit_repetitions.limit_id')->leftJoin( + 'components', 'components.id', '=', 'limits.component_id' + )->where('components.class', 'Budget')->where('components.user_id', Auth::user()->id)->first(['limit_repetitions.*']); } return null; - }); + } +); -Route::bind('piggybank', function($value, $route) - { - if(Auth::check()) { +Route::bind( + 'piggybank', function ($value, $route) { + if (Auth::check()) { return Piggybank:: - where('piggybanks.id', $value)-> - leftJoin('accounts','accounts.id','=','piggybanks.account_id')-> - where('accounts.user_id',Auth::user()->id)->first(['piggybanks.*']); + where('piggybanks.id', $value)->leftJoin('accounts', 'accounts.id', '=', 'piggybanks.account_id')->where('accounts.user_id', Auth::user()->id) + ->first(['piggybanks.*']); } return null; - }); - + } +); // a development route: Route::get('/dev', ['uses' => 'HomeController@jobDev']); // protected routes: -Route::group(['before' => 'auth'], function () { - - +Route::group( + ['before' => 'auth'], function () { // some date routes: - Route::get('/prev',['uses' => 'HomeController@sessionPrev', 'as' => 'sessionPrev']); - Route::get('/next',['uses' => 'HomeController@sessionNext', 'as' => 'sessionNext']); - Route::get('/jump/{range}',['uses' => 'HomeController@rangeJump','as' => 'rangeJump']); + Route::get('/prev', ['uses' => 'HomeController@sessionPrev', 'as' => 'sessionPrev']); + Route::get('/next', ['uses' => 'HomeController@sessionNext', 'as' => 'sessionNext']); + Route::get('/jump/{range}', ['uses' => 'HomeController@rangeJump', 'as' => 'rangeJump']); // account controller: - Route::get('/accounts/json/{what}', ['uses' => 'AccountController@json', 'as' => 'accounts.json'])->where('what','revenue|asset|expense'); - Route::get('/accounts/{what}', ['uses' => 'AccountController@index', 'as' => 'accounts.index'])->where('what','revenue|asset|expense'); - 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}',['uses' => 'AccountController@show','as' => 'accounts.show']); + Route::get('/accounts/json/{what}', ['uses' => 'AccountController@json', 'as' => 'accounts.json'])->where('what', 'revenue|asset|expense'); + Route::get('/accounts/{what}', ['uses' => 'AccountController@index', 'as' => 'accounts.index'])->where('what', 'revenue|asset|expense'); + 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}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); // budget controller: - Route::get('/budgets/date',['uses' => 'BudgetController@indexByDate','as' => 'budgets.index.date']); - Route::get('/budgets/budget',['uses' => 'BudgetController@indexByBudget','as' => 'budgets.index.budget']); - Route::get('/budgets/create',['uses' => 'BudgetController@create', 'as' => 'budgets.create']); + Route::get('/budgets/date', ['uses' => 'BudgetController@indexByDate', 'as' => 'budgets.index.date']); + Route::get('/budgets/budget', ['uses' => 'BudgetController@indexByBudget', 'as' => 'budgets.index.budget']); + Route::get('/budgets/create', ['uses' => 'BudgetController@create', 'as' => 'budgets.create']); - Route::get('/budgets/nobudget/{period}',['uses' => 'BudgetController@nobudget', 'as' => 'budgets.nobudget']); + Route::get('/budgets/nobudget/{period}', ['uses' => 'BudgetController@nobudget', 'as' => 'budgets.nobudget']); - Route::get('/budgets/show/{budget}/{limitrepetition?}',['uses' => 'BudgetController@show', 'as' => 'budgets.show']); - Route::get('/budgets/edit/{budget}',['uses' => 'BudgetController@edit', 'as' => 'budgets.edit']); - Route::get('/budgets/delete/{budget}',['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']); + Route::get('/budgets/show/{budget}/{limitrepetition?}', ['uses' => 'BudgetController@show', 'as' => 'budgets.show']); + Route::get('/budgets/edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'budgets.edit']); + Route::get('/budgets/delete/{budget}', ['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']); // category controller: - Route::get('/categories',['uses' => 'CategoryController@index','as' => 'categories.index']); - Route::get('/categories/create',['uses' => 'CategoryController@create','as' => 'categories.create']); - Route::get('/categories/show/{category}',['uses' => 'CategoryController@show','as' => 'categories.show']); - Route::get('/categories/edit/{category}',['uses' => 'CategoryController@edit','as' => 'categories.edit']); - Route::get('/categories/delete/{category}',['uses' => 'CategoryController@delete','as' => 'categories.delete']); + Route::get('/categories', ['uses' => 'CategoryController@index', 'as' => 'categories.index']); + Route::get('/categories/create', ['uses' => 'CategoryController@create', 'as' => 'categories.create']); + Route::get('/categories/show/{category}', ['uses' => 'CategoryController@show', 'as' => 'categories.show']); + Route::get('/categories/edit/{category}', ['uses' => 'CategoryController@edit', 'as' => 'categories.edit']); + Route::get('/categories/delete/{category}', ['uses' => 'CategoryController@delete', 'as' => 'categories.delete']); // google chart controller Route::get('/chart/home/account', ['uses' => 'GoogleChartController@allAccountsBalanceChart']); @@ -174,10 +168,8 @@ Route::group(['before' => 'auth'], function () { Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); - - Route::get('/chart/home/info/{accountnameA}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']); - Route::get('/chart/categories/show/{category}', ['uses' => 'ChartController@categoryShowChart','as' => 'chart.showcategory']); + Route::get('/chart/categories/show/{category}', ['uses' => 'ChartController@categoryShowChart', 'as' => 'chart.showcategory']); // (new charts for budgets) Route::get('/chart/budget/{budget}/default', ['uses' => 'ChartController@budgetDefault', 'as' => 'chart.budget.default']); @@ -200,58 +192,60 @@ Route::group(['before' => 'auth'], function () { Route::get('/json/recurringjournals/{recurring}', ['uses' => 'JsonController@recurringjournals', 'as' => 'json.recurringjournals']); // limit controller: - Route::get('/budgets/limits/create/{budget?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']); - Route::get('/budgets/limits/delete/{limit}',['uses' => 'LimitController@delete','as' => 'budgets.limits.delete']); - Route::get('/budgets/limits/edit/{limit}',['uses' => 'LimitController@edit','as' => 'budgets.limits.edit']); + Route::get('/budgets/limits/create/{budget?}', ['uses' => 'LimitController@create', 'as' => 'budgets.limits.create']); + Route::get('/budgets/limits/delete/{limit}', ['uses' => 'LimitController@delete', 'as' => 'budgets.limits.delete']); + Route::get('/budgets/limits/edit/{limit}', ['uses' => 'LimitController@edit', 'as' => 'budgets.limits.edit']); - Route::get('/migrate',['uses' => 'MigrateController@index', 'as' => 'migrate.index']); + Route::get('/migrate', ['uses' => 'MigrateController@index', 'as' => 'migrate.index']); // piggy bank controller - Route::get('/piggybanks',['uses' => 'PiggybankController@piggybanks','as' => 'piggybanks.index.piggybanks']); - Route::get('/repeated',['uses' => 'PiggybankController@repeated','as' => 'piggybanks.index.repeated']); - Route::get('/piggybanks/create/piggybank', ['uses' => 'PiggybankController@createPiggybank','as' => 'piggybanks.create.piggybank']); - Route::get('/piggybanks/create/repeated', ['uses' => 'PiggybankController@createRepeated','as' => 'piggybanks.create.repeated']); - Route::get('/piggybanks/addMoney/{piggybank}', ['uses' => 'PiggybankController@addMoney','as' => 'piggybanks.amount.add']); - Route::get('/piggybanks/removeMoney/{piggybank}', ['uses' => 'PiggybankController@removeMoney','as' => 'piggybanks.amount.remove']); - Route::get('/piggybanks/show/{piggybank}', ['uses' => 'PiggybankController@show','as' => 'piggybanks.show']); - Route::get('/piggybanks/edit/{piggybank}', ['uses' => 'PiggybankController@edit','as' => 'piggybanks.edit']); - Route::get('/piggybanks/delete/{piggybank}', ['uses' => 'PiggybankController@delete','as' => 'piggybanks.delete']); - Route::post('/piggybanks/updateAmount/{piggybank}',['uses' => 'PiggybankController@updateAmount','as' => 'piggybanks.updateAmount']); + Route::get('/piggybanks', ['uses' => 'PiggybankController@index', 'as' => 'piggybanks.index']); +// Route::get('/repeated',['uses' => 'PiggybankController@repeated','as' => 'piggybanks.index.repeated']); +// Route::get('/piggybanks/create/piggybank', ['uses' => 'PiggybankController@createPiggybank','as' => 'piggybanks.create.piggybank']); +// Route::get('/piggybanks/create/repeated', ['uses' => 'PiggybankController@createRepeated','as' => 'piggybanks.create.repeated']); +// Route::get('/piggybanks/addMoney/{piggybank}', ['uses' => 'PiggybankController@addMoney','as' => 'piggybanks.amount.add']); +// Route::get('/piggybanks/removeMoney/{piggybank}', ['uses' => 'PiggybankController@removeMoney','as' => 'piggybanks.amount.remove']); +// Route::get('/piggybanks/show/{piggybank}', ['uses' => 'PiggybankController@show','as' => 'piggybanks.show']); +// Route::get('/piggybanks/edit/{piggybank}', ['uses' => 'PiggybankController@edit','as' => 'piggybanks.edit']); +// Route::get('/piggybanks/delete/{piggybank}', ['uses' => 'PiggybankController@delete','as' => 'piggybanks.delete']); +// Route::post('/piggybanks/updateAmount/{piggybank}',['uses' => 'PiggybankController@updateAmount','as' => 'piggybanks.updateAmount']); // preferences controller Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']); //profile controller Route::get('/profile', ['uses' => 'ProfileController@index', 'as' => 'profile']); - Route::get('/profile/change-password',['uses' => 'ProfileController@changePassword', 'as' => 'change-password']); + Route::get('/profile/change-password', ['uses' => 'ProfileController@changePassword', 'as' => 'change-password']); // recurring transactions controller - Route::get('/recurring',['uses' => 'RecurringController@index', 'as' => 'recurring.index']); - Route::get('/recurring/show/{recurring}',['uses' => 'RecurringController@show', 'as' => 'recurring.show']); - Route::get('/recurring/rescan/{recurring}',['uses' => 'RecurringController@rescan', 'as' => 'recurring.rescan']); - Route::get('/recurring/create',['uses' => 'RecurringController@create', 'as' => 'recurring.create']); - Route::get('/recurring/edit/{recurring}',['uses' => 'RecurringController@edit','as' => 'recurring.edit']); - Route::get('/recurring/delete/{recurring}',['uses' => 'RecurringController@delete','as' => 'recurring.delete']); + Route::get('/recurring', ['uses' => 'RecurringController@index', 'as' => 'recurring.index']); + Route::get('/recurring/show/{recurring}', ['uses' => 'RecurringController@show', 'as' => 'recurring.show']); + Route::get('/recurring/rescan/{recurring}', ['uses' => 'RecurringController@rescan', 'as' => 'recurring.rescan']); + Route::get('/recurring/create', ['uses' => 'RecurringController@create', 'as' => 'recurring.create']); + Route::get('/recurring/edit/{recurring}', ['uses' => 'RecurringController@edit', 'as' => 'recurring.edit']); + Route::get('/recurring/delete/{recurring}', ['uses' => 'RecurringController@delete', 'as' => 'recurring.delete']); // report controller: - Route::get('/reports',['uses' => 'ReportController@index','as' => 'reports.index']); + Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']); // search controller: - Route::get('/search',['uses' => 'SearchController@index','as' => 'search']); + Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']); // transaction controller: - Route::get('/transactions/create/{what}', ['uses' => 'TransactionController@create', 'as' => 'transactions.create'])->where(['what' => 'withdrawal|deposit|transfer']); - Route::get('/transaction/show/{tj}',['uses' => 'TransactionController@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('/transactions/index',['uses' => 'TransactionController@index','as' => 'transactions.index']); - Route::get('/transactions/expenses',['uses' => 'TransactionController@expenses','as' => 'transactions.expenses']); - Route::get('/transactions/revenue',['uses' => 'TransactionController@revenue','as' => 'transactions.revenue']); - Route::get('/transactions/transfers',['uses' => 'TransactionController@transfers','as' => 'transactions.transfers']); + Route::get('/transactions/create/{what}', ['uses' => 'TransactionController@create', 'as' => 'transactions.create'])->where( + ['what' => 'withdrawal|deposit|transfer'] + ); + Route::get('/transaction/show/{tj}', ['uses' => 'TransactionController@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('/transactions/index', ['uses' => 'TransactionController@index', 'as' => 'transactions.index']); + Route::get('/transactions/expenses', ['uses' => 'TransactionController@expenses', 'as' => 'transactions.expenses']); + Route::get('/transactions/revenue', ['uses' => 'TransactionController@revenue', 'as' => 'transactions.revenue']); + Route::get('/transactions/transfers', ['uses' => 'TransactionController@transfers', 'as' => 'transactions.transfers']); - Route::get('/transactions/expenses',['uses' => 'TransactionController@expenses','as' => 'transactions.index.withdrawal']); - Route::get('/transactions/revenue',['uses' => 'TransactionController@revenue','as' => 'transactions.index.deposit']); - Route::get('/transactions/transfers',['uses' => 'TransactionController@transfers','as' => 'transactions.index.transfer']); + Route::get('/transactions/expenses', ['uses' => 'TransactionController@expenses', 'as' => 'transactions.index.withdrawal']); + Route::get('/transactions/revenue', ['uses' => 'TransactionController@revenue', 'as' => 'transactions.index.deposit']); + Route::get('/transactions/transfers', ['uses' => 'TransactionController@transfers', 'as' => 'transactions.index.transfer']); // user controller Route::get('/logout', ['uses' => 'UserController@logout', 'as' => 'logout']); @@ -260,36 +254,37 @@ Route::group(['before' => 'auth'], function () { ); // protected + csrf routes (POST) -Route::group(['before' => 'csrf|auth'], function () { +Route::group( + ['before' => 'csrf|auth'], function () { // account controller: Route::post('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']); Route::post('/accounts/update/{account}', ['uses' => 'AccountController@update', 'as' => 'accounts.update']); Route::post('/accounts/destroy/{account}', ['uses' => 'AccountController@destroy', 'as' => 'accounts.destroy']); // budget controller: - Route::post('/budgets/store',['uses' => 'BudgetController@store', 'as' => 'budgets.store']); + Route::post('/budgets/store', ['uses' => 'BudgetController@store', 'as' => 'budgets.store']); Route::post('/budgets/update/{budget}', ['uses' => 'BudgetController@update', 'as' => 'budgets.update']); Route::post('/budgets/destroy/{budget}', ['uses' => 'BudgetController@destroy', 'as' => 'budgets.destroy']); // category controller - Route::post('/categories/store',['uses' => 'CategoryController@store', 'as' => 'categories.store']); + Route::post('/categories/store', ['uses' => 'CategoryController@store', 'as' => 'categories.store']); Route::post('/categories/update/{category}', ['uses' => 'CategoryController@update', 'as' => 'categories.update']); Route::post('/categories/destroy/{category}', ['uses' => 'CategoryController@destroy', 'as' => 'categories.destroy']); // limit controller: Route::post('/budgets/limits/store/{budget?}', ['uses' => 'LimitController@store', 'as' => 'budgets.limits.store']); - Route::post('/budgets/limits/destroy/{limit}',['uses' => 'LimitController@destroy','as' => 'budgets.limits.destroy']); - Route::post('/budgets/limits/update/{limit}',['uses' => 'LimitController@update','as' => 'budgets.limits.update']); + Route::post('/budgets/limits/destroy/{limit}', ['uses' => 'LimitController@destroy', 'as' => 'budgets.limits.destroy']); + Route::post('/budgets/limits/update/{limit}', ['uses' => 'LimitController@update', 'as' => 'budgets.limits.update']); - Route::post('/migrate/upload',['uses' => 'MigrateController@upload', 'as' => 'migrate.upload']); + Route::post('/migrate/upload', ['uses' => 'MigrateController@upload', 'as' => 'migrate.upload']); // piggy bank controller - Route::post('/piggybanks/store/piggybank',['uses' => 'PiggybankController@storePiggybank','as' => 'piggybanks.store.piggybank']); - Route::post('/piggybanks/store/repeated',['uses' => 'PiggybankController@storeRepeated','as' => 'piggybanks.store.repeated']); - Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update','as' => 'piggybanks.update']); - Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy','as' => 'piggybanks.destroy']); - Route::post('/piggybanks/mod/{piggybank}', ['uses' => 'PiggybankController@modMoney','as' => 'piggybanks.modMoney']); + Route::post('/piggybanks/store/piggybank', ['uses' => 'PiggybankController@storePiggybank', 'as' => 'piggybanks.store.piggybank']); + Route::post('/piggybanks/store/repeated', ['uses' => 'PiggybankController@storeRepeated', 'as' => 'piggybanks.store.repeated']); + Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); + Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); + Route::post('/piggybanks/mod/{piggybank}', ['uses' => 'PiggybankController@modMoney', 'as' => 'piggybanks.modMoney']); // preferences controller @@ -299,20 +294,23 @@ Route::group(['before' => 'csrf|auth'], function () { Route::post('/profile/change-password', ['uses' => 'ProfileController@postChangePassword']); // recurring controller - Route::post('/recurring/store',['uses' => 'RecurringController@store', 'as' => 'recurring.store']); - Route::post('/recurring/update/{recurring}',['uses' => 'RecurringController@update','as' => 'recurring.update']); - Route::post('/recurring/destroy/{recurring}',['uses' => 'RecurringController@destroy','as' => 'recurring.destroy']); + Route::post('/recurring/store', ['uses' => 'RecurringController@store', 'as' => 'recurring.store']); + Route::post('/recurring/update/{recurring}', ['uses' => 'RecurringController@update', 'as' => 'recurring.update']); + Route::post('/recurring/destroy/{recurring}', ['uses' => 'RecurringController@destroy', 'as' => 'recurring.destroy']); // transaction controller: - Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])->where(['what' => 'withdrawal|deposit|transfer']); - Route::post('/transaction/update/{tj}',['uses' => 'TransactionController@update','as' => 'transactions.update']); - Route::post('/transaction/destroy/{tj}',['uses' => 'TransactionController@destroy','as' => 'transactions.destroy']); + Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])->where( + ['what' => 'withdrawal|deposit|transfer'] + ); + Route::post('/transaction/update/{tj}', ['uses' => 'TransactionController@update', 'as' => 'transactions.update']); + Route::post('/transaction/destroy/{tj}', ['uses' => 'TransactionController@destroy', 'as' => 'transactions.destroy']); } ); // guest routes: -Route::group(['before' => 'guest'], function () { +Route::group( + ['before' => 'guest'], function () { // user controller Route::get('/login', ['uses' => 'UserController@login', 'as' => 'login']); Route::get('/register', ['uses' => 'UserController@register', 'as' => 'register']); @@ -325,7 +323,8 @@ Route::group(['before' => 'guest'], function () { ); // guest + csrf routes: -Route::group(['before' => 'csrf|guest'], function () { +Route::group( + ['before' => 'csrf|guest'], function () { // user controller Route::post('/login', ['uses' => 'UserController@postLogin']); From f7722c118937ebbb75281bd4e336ba3d633ddc1c Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 30 Oct 2014 19:26:52 +0100 Subject: [PATCH 31/57] Clean up piggy bank controller. --- app/controllers/PiggybankController.php | 682 ++++++++++++------------ 1 file changed, 344 insertions(+), 338 deletions(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 71f4e572b3..013f0a7e3c 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -1,88 +1,80 @@ _repository = $repository; - $this->_accounts = $accounts; - } - - /** - * @param Piggybank $piggyBank * - * @return $this */ - public function addMoney(Piggybank $piggyBank) + public function __construct() { - $what = 'add'; - $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); - $maxRemove = null; - - return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with( - 'maxRemove', $maxRemove - )->with('piggybank', $piggyBank); } +// /** +// * @param Piggybank $piggyBank +// * +// * @return $this +// */ +// public function addMoney(Piggybank $piggyBank) +// { +// throw new NotImplementedException; +// $what = 'add'; +// $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); +// $maxRemove = null; +// +// return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with( +// 'maxRemove', $maxRemove +// )->with('piggybank', $piggyBank); +// } + /** * @return $this */ - public function createPiggybank() + public function create() { - /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ - $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); - - - $periods = Config::get('firefly.piggybank_periods'); - $list = $this->_accounts->getActiveDefault(); - $accounts = $toolkit->makeSelectList($list); - - View::share('title', 'Piggy banks'); - View::share('subTitle', 'Create new'); - View::share('mainTitleIcon', 'fa-sort-amount-asc'); - - return View::make('piggybanks.create-piggybank')->with('accounts', $accounts) - ->with('periods', $periods); + throw new NotImplementedException; +// /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ +// $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); +// +// +// $periods = Config::get('firefly.piggybank_periods'); +// $list = $this->_accounts->getActiveDefault(); +// $accounts = $toolkit->makeSelectList($list); +// +// View::share('title', 'Piggy banks'); +// View::share('subTitle', 'Create new'); +// View::share('mainTitleIcon', 'fa-sort-amount-asc'); +// +// return View::make('piggybanks.create-piggybank')->with('accounts', $accounts) +// ->with('periods', $periods); } - /** - * @return $this - */ - public function createRepeated() - { - /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ - $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); - - $periods = Config::get('firefly.piggybank_periods'); - $list = $this->_accounts->getActiveDefault(); - $accounts = $toolkit->makeSelectList($list); - - View::share('title', 'Repeated expenses'); - View::share('subTitle', 'Create new'); - View::share('mainTitleIcon', 'fa-rotate-right'); - - return View::make('piggybanks.create-repeated')->with('accounts', $accounts)->with('periods', $periods); - } +// /** +// * @return $this +// */ +// public function createRepeated() +// { +// throw new NotImplementedException; +// /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ +// $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); +// +// $periods = Config::get('firefly.piggybank_periods'); +// $list = $this->_accounts->getActiveDefault(); +// $accounts = $toolkit->makeSelectList($list); +// +// View::share('title', 'Repeated expenses'); +// View::share('subTitle', 'Create new'); +// View::share('mainTitleIcon', 'fa-rotate-right'); +// +// return View::make('piggybanks.create-repeated')->with('accounts', $accounts)->with('periods', $periods); +// } /** * @param Piggybank $piggyBank @@ -91,16 +83,17 @@ class PiggybankController extends BaseController */ public function delete(Piggybank $piggyBank) { - View::share('subTitle', 'Delete "' . $piggyBank->name . '"'); - if ($piggyBank->repeats == 1) { - View::share('title', 'Repeated expenses'); - View::share('mainTitleIcon', 'fa-rotate-right'); - } else { - View::share('title', 'Piggy banks'); - View::share('mainTitleIcon', 'fa-sort-amount-asc'); - } - - return View::make('piggybanks.delete')->with('piggybank', $piggyBank); + throw new NotImplementedException; +// View::share('subTitle', 'Delete "' . $piggyBank->name . '"'); +// if ($piggyBank->repeats == 1) { +// View::share('title', 'Repeated expenses'); +// View::share('mainTitleIcon', 'fa-rotate-right'); +// } else { +// View::share('title', 'Piggy banks'); +// View::share('mainTitleIcon', 'fa-sort-amount-asc'); +// } +// +// return View::make('piggybanks.delete')->with('piggybank', $piggyBank); } /** @@ -110,19 +103,20 @@ class PiggybankController extends BaseController */ public function destroy(Piggybank $piggyBank) { - Event::fire('piggybanks.destroy', [$piggyBank]); - if ($piggyBank->repeats == 1) { - $route = 'piggybanks.index.repeated'; - $message = 'Repeated expense'; - } else { - $route = 'piggybanks.index.piggybanks'; - $message = 'Piggybank'; - } - $this->_repository->destroy($piggyBank); - - Session::flash('success', $message . ' deleted.'); - - return Redirect::route($route); + throw new NotImplementedException; +// Event::fire('piggybanks.destroy', [$piggyBank]); +// if ($piggyBank->repeats == 1) { +// $route = 'piggybanks.index.repeated'; +// $message = 'Repeated expense'; +// } else { +// $route = 'piggybanks.index.piggybanks'; +// $message = 'Piggybank'; +// } +// $this->_repository->destroy($piggyBank); +// +// Session::flash('success', $message . ' deleted.'); +// +// return Redirect::route($route); } /** @@ -132,280 +126,292 @@ class PiggybankController extends BaseController */ public function edit(Piggybank $piggyBank) { - /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ - $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); - - $list = $this->_accounts->getActiveDefault(); - $accounts = $toolkit->makeSelectList($list); - $periods = Config::get('firefly.piggybank_periods'); - - - View::share('subTitle', 'Edit "' . $piggyBank->name . '"'); - - - if ($piggyBank->repeats == 1) { - View::share('title', 'Repeated expenses'); - View::share('mainTitleIcon', 'fa-rotate-left'); - - return View::make('piggybanks.edit-repeated')->with('piggybank', $piggyBank)->with('accounts', $accounts) - ->with('periods', $periods); - } else { - // piggy bank. - View::share('title', 'Piggy banks'); - View::share('mainTitleIcon', 'fa-sort-amount-asc'); - - return View::make('piggybanks.edit-piggybank')->with('piggybank', $piggyBank)->with('accounts', $accounts) - ->with('periods', $periods); - } + throw new NotImplementedException; +// /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ +// $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); +// +// $list = $this->_accounts->getActiveDefault(); +// $accounts = $toolkit->makeSelectList($list); +// $periods = Config::get('firefly.piggybank_periods'); +// +// +// View::share('subTitle', 'Edit "' . $piggyBank->name . '"'); +// +// +// if ($piggyBank->repeats == 1) { +// View::share('title', 'Repeated expenses'); +// View::share('mainTitleIcon', 'fa-rotate-left'); +// +// return View::make('piggybanks.edit-repeated')->with('piggybank', $piggyBank)->with('accounts', $accounts) +// ->with('periods', $periods); +// } else { +// // piggy bank. +// View::share('title', 'Piggy banks'); +// View::share('mainTitleIcon', 'fa-sort-amount-asc'); +// +// return View::make('piggybanks.edit-piggybank')->with('piggybank', $piggyBank)->with('accounts', $accounts) +// ->with('periods', $periods); +// } } - /** - * @param Piggybank $piggyBank - * - * @return \Illuminate\Http\RedirectResponse - * @throws Firefly\Exception\FireflyException - */ - public function modMoney(Piggybank $piggyBank) +// /** +// * @param Piggybank $piggyBank +// * +// * @return \Illuminate\Http\RedirectResponse +// * @throws Firefly\Exception\FireflyException +// */ +// public function modMoney(Piggybank $piggyBank) +// { +// throw new NotImplementedException; +// $amount = floatval(Input::get('amount')); +// switch (Input::get('what')) { +// default: +// throw new FireflyException('No such action'); +// break; +// case 'add': +// $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); +// if (round($amount, 2) <= round(min($maxAdd, $piggyBank->targetamount), 2)) { +// Session::flash('success', 'Amount updated!'); +// $this->_repository->modifyAmount($piggyBank, $amount); +// Event::fire('piggybanks.modifyAmountAdd', [$piggyBank, $amount]); +// } else { +// Session::flash('warning', 'Could not!'); +// } +// break; +// case 'remove': +// $rep = $piggyBank->currentRelevantRep(); +// $maxRemove = $rep->currentamount; +// if (round($amount, 2) <= round($maxRemove, 2)) { +// Session::flash('success', 'Amount updated!'); +// $this->_repository->modifyAmount($piggyBank, ($amount * -1)); +// Event::fire('piggybanks.modifyAmountRemove', [$piggyBank, ($amount * -1)]); +// } else { +// Session::flash('warning', 'Could not!'); +// } +// break; +// } +// if ($piggyBank->repeats == 1) { +// $route = 'piggybanks.index.repeated'; +// +// } else { +// $route = 'piggybanks.index.piggybanks'; +// } +// return Redirect::route($route); +// } + +// /** +// * @return $this +// */ + public function index() { - $amount = floatval(Input::get('amount')); - switch (Input::get('what')) { - default: - throw new FireflyException('No such action'); - break; - case 'add': - $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); - if (round($amount, 2) <= round(min($maxAdd, $piggyBank->targetamount), 2)) { - Session::flash('success', 'Amount updated!'); - $this->_repository->modifyAmount($piggyBank, $amount); - Event::fire('piggybanks.modifyAmountAdd', [$piggyBank, $amount]); - } else { - Session::flash('warning', 'Could not!'); - } - break; - case 'remove': - $rep = $piggyBank->currentRelevantRep(); - $maxRemove = $rep->currentamount; - if (round($amount, 2) <= round($maxRemove, 2)) { - Session::flash('success', 'Amount updated!'); - $this->_repository->modifyAmount($piggyBank, ($amount * -1)); - Event::fire('piggybanks.modifyAmountRemove', [$piggyBank, ($amount * -1)]); - } else { - Session::flash('warning', 'Could not!'); - } - break; - } - if ($piggyBank->repeats == 1) { - $route = 'piggybanks.index.repeated'; + - } else { - $route = 'piggybanks.index.piggybanks'; - } - return Redirect::route($route); + + throw new NotImplementedException; +// $countRepeating = $this->_repository->countRepeating(); +// $countNonRepeating = $this->_repository->countNonrepeating(); +// +// $piggybanks = $this->_repository->get(); +// +// // get the accounts with each piggy bank and check their balance; Fireflyy might needs to +// // show the user a correction. +// +// $accounts = []; +// /** @var \Piggybank $piggybank */ +// foreach ($piggybanks as $piggybank) { +// $account = $piggybank->account; +// $id = $account->id; +// if (!isset($accounts[$id])) { +// $account->leftOnAccount = $this->_repository->leftOnAccount($account); +// $accounts[$id] = [ +// 'account' => $account, +// 'left' => $this->_repository->leftOnAccount($account), +// 'tosave' => $piggybank->targetamount, +// 'saved' => $piggybank->currentRelevantRep()->currentamount +// ]; +// } else { +// $accounts[$id]['tosave'] += $piggybank->targetamount; +// $accounts[$id]['saved'] += $piggybank->currentRelevantRep()->currentamount; +// } +// } +// +// View::share('title', 'Piggy banks'); +// View::share('subTitle', 'Save for big expenses'); +// View::share('mainTitleIcon', 'fa-sort-amount-asc'); +// +// return View::make('piggybanks.index')->with('piggybanks', $piggybanks) +// ->with('countRepeating', $countRepeating) +// ->with('countNonRepeating', $countNonRepeating) +// ->with('accounts', $accounts); } - /** - * @return $this - */ - public function piggybanks() - { - $countRepeating = $this->_repository->countRepeating(); - $countNonRepeating = $this->_repository->countNonrepeating(); +// /** +// * @param Piggybank $piggyBank +// * +// * @return $this +// */ +// public function removeMoney(Piggybank $piggyBank) +// { +// $what = 'remove'; +// $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); +// $maxRemove = $piggyBank->currentRelevantRep()->currentamount; +// +// return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with( +// 'maxRemove', $maxRemove +// )->with('piggybank', $piggyBank); +// } - $piggybanks = $this->_repository->get(); +// /** +// * @return $this +// */ +// public function repeated() +// { +// $countRepeating = $this->_repository->countRepeating(); +// $countNonRepeating = $this->_repository->countNonrepeating(); +// +// $piggybanks = $this->_repository->get(); +// +// // get the accounts with each piggy bank and check their balance; Fireflyy might needs to +// // show the user a correction. +// +// $accounts = []; +// /** @var \Piggybank $piggybank */ +// foreach ($piggybanks as $piggybank) { +// $account = $piggybank->account; +// $id = $account->id; +// if (!isset($accounts[$id])) { +// $account->leftOnAccount = $this->_repository->leftOnAccount($account); +// $accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)]; +// } +// } +// +// View::share('title', 'Repeated expenses'); +// View::share('subTitle', 'Save for returning bills'); +// View::share('mainTitleIcon', 'fa-rotate-left'); +// +// +// return View::make('piggybanks.index')->with('piggybanks', $piggybanks) +// ->with('countRepeating', $countRepeating) +// ->with('countNonRepeating', $countNonRepeating) +// ->with('accounts', $accounts); +// } - // get the accounts with each piggy bank and check their balance; Fireflyy might needs to - // show the user a correction. - - $accounts = []; - /** @var \Piggybank $piggybank */ - foreach ($piggybanks as $piggybank) { - $account = $piggybank->account; - $id = $account->id; - if (!isset($accounts[$id])) { - $account->leftOnAccount = $this->_repository->leftOnAccount($account); - $accounts[$id] = [ - 'account' => $account, - 'left' => $this->_repository->leftOnAccount($account), - 'tosave' => $piggybank->targetamount, - 'saved' => $piggybank->currentRelevantRep()->currentamount - ]; - } else { - $accounts[$id]['tosave'] += $piggybank->targetamount; - $accounts[$id]['saved'] += $piggybank->currentRelevantRep()->currentamount; - } - } - - View::share('title', 'Piggy banks'); - View::share('subTitle', 'Save for big expenses'); - View::share('mainTitleIcon', 'fa-sort-amount-asc'); - - return View::make('piggybanks.index')->with('piggybanks', $piggybanks) - ->with('countRepeating', $countRepeating) - ->with('countNonRepeating', $countNonRepeating) - ->with('accounts', $accounts); - } - - /** - * @param Piggybank $piggyBank - * - * @return $this - */ - public function removeMoney(Piggybank $piggyBank) - { - $what = 'remove'; - $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); - $maxRemove = $piggyBank->currentRelevantRep()->currentamount; - - return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with( - 'maxRemove', $maxRemove - )->with('piggybank', $piggyBank); - } - - /** - * @return $this - */ - public function repeated() - { - $countRepeating = $this->_repository->countRepeating(); - $countNonRepeating = $this->_repository->countNonrepeating(); - - $piggybanks = $this->_repository->get(); - - // get the accounts with each piggy bank and check their balance; Fireflyy might needs to - // show the user a correction. - - $accounts = []; - /** @var \Piggybank $piggybank */ - foreach ($piggybanks as $piggybank) { - $account = $piggybank->account; - $id = $account->id; - if (!isset($accounts[$id])) { - $account->leftOnAccount = $this->_repository->leftOnAccount($account); - $accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)]; - } - } - - View::share('title', 'Repeated expenses'); - View::share('subTitle', 'Save for returning bills'); - View::share('mainTitleIcon', 'fa-rotate-left'); - - - return View::make('piggybanks.index')->with('piggybanks', $piggybanks) - ->with('countRepeating', $countRepeating) - ->with('countNonRepeating', $countNonRepeating) - ->with('accounts', $accounts); - } - - /** - * - */ +// /** +// * @param Piggybank $piggyBank +// * +// * @return $this +// * @throws NotImplementedException +// */ public function show(Piggybank $piggyBank) { - $leftOnAccount = $this->_repository->leftOnAccount($piggyBank->account); - $balance = $piggyBank->account->balance(); - - View::share('subTitle', $piggyBank->name); - - if ($piggyBank->repeats == 1) { - // repeated expense. - View::share('title', 'Repeated expenses'); - View::share('mainTitleIcon', 'fa-rotate-left'); - } else { - // piggy bank. - View::share('title', 'Piggy banks'); - View::share('mainTitleIcon', 'fa-sort-amount-asc'); - } - - return View::make('piggybanks.show')->with('piggyBank', $piggyBank)->with('leftOnAccount', $leftOnAccount) - ->with('balance', $balance); + throw new NotImplementedException; +// $leftOnAccount = $this->_repository->leftOnAccount($piggyBank->account); +// $balance = $piggyBank->account->balance(); +// +// View::share('subTitle', $piggyBank->name); +// +// if ($piggyBank->repeats == 1) { +// // repeated expense. +// View::share('title', 'Repeated expenses'); +// View::share('mainTitleIcon', 'fa-rotate-left'); +// } else { +// // piggy bank. +// View::share('title', 'Piggy banks'); +// View::share('mainTitleIcon', 'fa-sort-amount-asc'); +// } +// +// return View::make('piggybanks.show')->with('piggyBank', $piggyBank)->with('leftOnAccount', $leftOnAccount) +// ->with('balance', $balance); } - /** - * @return $this|\Illuminate\Http\RedirectResponse - */ - public function storePiggybank() +// /** +// * @return $this|\Illuminate\Http\RedirectResponse +// */ + public function store() { - $data = Input::all(); - unset($data['_token']); - - // extend the data array with the settings needed to create a piggy bank: - $data['repeats'] = 0; - $data['rep_times'] = 1; - $data['rep_every'] = 1; - $data['order'] = 0; - - $piggyBank = $this->_repository->store($data); - if (!is_null($piggyBank->id)) { - Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!'); - Event::fire('piggybanks.store', [$piggyBank]); - - return Redirect::route('piggybanks.index.piggybanks'); - - - } else { - Session::flash('error', 'Could not save piggy bank: ' . $piggyBank->errors()->first()); - - return Redirect::route('piggybanks.create.piggybank')->withInput()->withErrors($piggyBank->errors()); - } + throw new NotImplementedException; +// $data = Input::all(); +// unset($data['_token']); +// +// // extend the data array with the settings needed to create a piggy bank: +// $data['repeats'] = 0; +// $data['rep_times'] = 1; +// $data['rep_every'] = 1; +// $data['order'] = 0; +// +// $piggyBank = $this->_repository->store($data); +// if (!is_null($piggyBank->id)) { +// Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!'); +// Event::fire('piggybanks.store', [$piggyBank]); +// +// return Redirect::route('piggybanks.index.piggybanks'); +// +// +// } else { +// Session::flash('error', 'Could not save piggy bank: ' . $piggyBank->errors()->first()); +// +// return Redirect::route('piggybanks.create.piggybank')->withInput()->withErrors($piggyBank->errors()); +// } } - /** - * @return $this|\Illuminate\Http\RedirectResponse - */ - public function storeRepeated() - { +// /** +// * @return $this|\Illuminate\Http\RedirectResponse +// */ +// public function storeRepeated() +// { +// +// $data = Input::all(); +// unset($data['_token']); +// +// // extend the data array with the settings needed to create a repeated: +// $data['repeats'] = 1; +// $data['order'] = 0; +// +// $piggyBank = $this->_repository->store($data); +// if ($piggyBank->id) { +// Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!'); +// Event::fire('piggybanks.store', [$piggyBank]); +// return Redirect::route('piggybanks.index.repeated'); +// +// } else { +// Session::flash('error', 'Could not save piggy bank: ' . $piggyBank->errors()->first()); +// +// return Redirect::route('piggybanks.create.repeated')->withInput()->withErrors($piggyBank->errors()); +// } +// +// } - $data = Input::all(); - unset($data['_token']); - - // extend the data array with the settings needed to create a repeated: - $data['repeats'] = 1; - $data['order'] = 0; - - $piggyBank = $this->_repository->store($data); - if ($piggyBank->id) { - Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!'); - Event::fire('piggybanks.store', [$piggyBank]); - return Redirect::route('piggybanks.index.repeated'); - - } else { - Session::flash('error', 'Could not save piggy bank: ' . $piggyBank->errors()->first()); - - return Redirect::route('piggybanks.create.repeated')->withInput()->withErrors($piggyBank->errors()); - } - - } - - /** - * @param Piggybank $piggyBank - * - * @return $this|\Illuminate\Http\RedirectResponse - */ +// /** +// * @param Piggybank $piggyBank +// * +// * @return $this|\Illuminate\Http\RedirectResponse +// */ public function update(Piggybank $piggyBank) { - $piggyBank = $this->_repository->update($piggyBank, Input::all()); - if ($piggyBank->validate()) { - if ($piggyBank->repeats == 1) { - $route = 'piggybanks.index.repeated'; - $message = 'Repeated expense'; - } else { - $route = 'piggybanks.index.piggybanks'; - $message = 'Piggy bank'; - } - - - Session::flash('success', $message . ' "' . $piggyBank->name . '" updated.'); - Event::fire('piggybanks.update', [$piggyBank]); - - return Redirect::route($route); - } else { - Session::flash('error', 'Could not update piggy bank: ' . $piggyBank->errors()->first()); - - return Redirect::route('piggybanks.edit', $piggyBank->id)->withErrors($piggyBank->errors())->withInput(); - } + throw new NotImplementedException; +// $piggyBank = $this->_repository->update($piggyBank, Input::all()); +// if ($piggyBank->validate()) { +// if ($piggyBank->repeats == 1) { +// $route = 'piggybanks.index.repeated'; +// $message = 'Repeated expense'; +// } else { +// $route = 'piggybanks.index.piggybanks'; +// $message = 'Piggy bank'; +// } +// +// +// Session::flash('success', $message . ' "' . $piggyBank->name . '" updated.'); +// Event::fire('piggybanks.update', [$piggyBank]); +// +// return Redirect::route($route); +// } else { +// Session::flash('error', 'Could not update piggy bank: ' . $piggyBank->errors()->first()); +// +// return Redirect::route('piggybanks.edit', $piggyBank->id)->withErrors($piggyBank->errors())->withInput(); +// } } From 3231effd2012e74ab1e36c046aaff240f79daf3a Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Fri, 31 Oct 2014 07:32:43 +0100 Subject: [PATCH 32/57] Cleanup and fix everything related to piggy banks. --- app/controllers/PiggybankController.php | 177 ++++++++--- .../Database/Ifaces/PiggybankInterface.php | 19 ++ app/lib/FireflyIII/Database/Piggybank.php | 135 ++++++++ app/routes.php | 14 +- app/views/partials/menu.blade.php | 8 +- app/views/piggybanks/add.blade.php | 24 ++ ...ade.php => create-piggybank.blade.old.php} | 0 ...lade.php => create-repeated.blade.old.php} | 0 ...{delete.blade.php => delete.blade.old.php} | 0 ...blade.php => edit-piggybank.blade.old.php} | 0 ....blade.php => edit-repeated.blade.old.php} | 0 app/views/piggybanks/index.blade.old.php | 223 +++++++++++++ app/views/piggybanks/index.blade.php | 296 ++++++------------ ...t.blade.php => modifyAmount.blade.old.php} | 0 app/views/piggybanks/remove.blade.php | 24 ++ .../{show.blade.php => show.blade.old.php} | 0 public/assets/javascript/firefly/index.js | 3 - .../assets/javascript/firefly/piggybanks.js | 19 ++ 18 files changed, 697 insertions(+), 245 deletions(-) create mode 100644 app/lib/FireflyIII/Database/Ifaces/PiggybankInterface.php create mode 100644 app/lib/FireflyIII/Database/Piggybank.php create mode 100644 app/views/piggybanks/add.blade.php rename app/views/piggybanks/{create-piggybank.blade.php => create-piggybank.blade.old.php} (100%) rename app/views/piggybanks/{create-repeated.blade.php => create-repeated.blade.old.php} (100%) rename app/views/piggybanks/{delete.blade.php => delete.blade.old.php} (100%) rename app/views/piggybanks/{edit-piggybank.blade.php => edit-piggybank.blade.old.php} (100%) rename app/views/piggybanks/{edit-repeated.blade.php => edit-repeated.blade.old.php} (100%) create mode 100644 app/views/piggybanks/index.blade.old.php rename app/views/piggybanks/{modifyAmount.blade.php => modifyAmount.blade.old.php} (100%) create mode 100644 app/views/piggybanks/remove.blade.php rename app/views/piggybanks/{show.blade.php => show.blade.old.php} (100%) create mode 100644 public/assets/javascript/firefly/piggybanks.js diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 013f0a7e3c..798237af50 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -2,6 +2,7 @@ use Firefly\Exception\FireflyException; use FireflyIII\Exception\NotImplementedException; +use Illuminate\Support\Collection; /** * Class PiggybankController @@ -17,45 +18,16 @@ class PiggybankController extends BaseController { } -// /** -// * @param Piggybank $piggyBank -// * -// * @return $this -// */ -// public function addMoney(Piggybank $piggyBank) -// { -// throw new NotImplementedException; -// $what = 'add'; -// $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); -// $maxRemove = null; -// -// return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with( -// 'maxRemove', $maxRemove -// )->with('piggybank', $piggyBank); -// } - /** - * @return $this + * @throws NotImplementedException */ public function create() { throw new NotImplementedException; -// /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ -// $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); -// -// -// $periods = Config::get('firefly.piggybank_periods'); -// $list = $this->_accounts->getActiveDefault(); -// $accounts = $toolkit->makeSelectList($list); -// -// View::share('title', 'Piggy banks'); -// View::share('subTitle', 'Create new'); -// View::share('mainTitleIcon', 'fa-sort-amount-asc'); -// -// return View::make('piggybanks.create-piggybank')->with('accounts', $accounts) -// ->with('periods', $periods); } + + // /** // * @return $this // */ @@ -201,15 +173,127 @@ class PiggybankController extends BaseController // return Redirect::route($route); // } -// /** -// * @return $this -// */ + + /** + * @param Piggybank $piggybank + * + * @return $this + */ + public function add(Piggybank $piggybank) + { + /** @var \FireflyIII\Database\Piggybank $acct */ + $repos = App::make('FireflyIII\Database\Piggybank'); + + $leftOnAccount = $repos->leftOnAccount($piggybank->account); + $savedSoFar = $piggybank->currentRelevantRep()->currentamount; + $leftToSave = $piggybank->targetamount - $savedSoFar; + $amount = min($leftOnAccount, $leftToSave); + + + return View::make('piggybanks.add', compact('piggybank'))->with('maxAmount', $amount); + } + + /** + * @param Piggybank $piggybank + * + * @return \Illuminate\Http\RedirectResponse + */ + public function postAdd(Piggybank $piggybank) + { + $amount = floatval(Input::get('amount')); + + /** @var \FireflyIII\Database\Piggybank $acct */ + $repos = App::make('FireflyIII\Database\Piggybank'); + + $leftOnAccount = $repos->leftOnAccount($piggybank->account); + $savedSoFar = $piggybank->currentRelevantRep()->currentamount; + $leftToSave = $piggybank->targetamount - $savedSoFar; + $maxAmount = min($leftOnAccount, $leftToSave); + + if ($amount <= $maxAmount) { + $repetition = $piggybank->currentRelevantRep(); + $repetition->currentamount += $amount; + $repetition->save(); + Session::flash('success', 'Added ' . mf($amount, false) . ' to "' . e($piggybank->name) . '".'); + } else { + Session::flash('error', 'Could not add ' . mf($amount, false) . ' to "' . e($piggybank->name) . '".'); + } + return Redirect::route('piggybanks.index'); + } + + /** + * @param Piggybank $piggybank + * + * @return \Illuminate\View\View + */ + public function remove(Piggybank $piggybank) + { + return View::make('piggybanks.remove', compact('piggybank')); + } + + /** + * @param Piggybank $piggybank + * + * @return \Illuminate\Http\RedirectResponse + */ + public function postRemove(Piggybank $piggybank) + { + $amount = floatval(Input::get('amount')); + + $savedSoFar = $piggybank->currentRelevantRep()->currentamount; + + if ($amount <= $savedSoFar) { + $repetition = $piggybank->currentRelevantRep(); + $repetition->currentamount -= $amount; + $repetition->save(); + Session::flash('success', 'Removed ' . mf($amount, false) . ' from "' . e($piggybank->name) . '".'); + } else { + Session::flash('error', 'Could not remove ' . mf($amount, false) . ' from "' . e($piggybank->name) . '".'); + } + return Redirect::route('piggybanks.index'); + } + public function index() { - + /** @var \FireflyIII\Database\Piggybank $repos */ + $repos = App::make('FireflyIII\Database\Piggybank'); + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); - throw new NotImplementedException; + /** @var Collection $piggybanks */ + $piggybanks = $repos->get(); + + $accounts = []; + /** @var Piggybank $piggybank */ + foreach ($piggybanks as $piggybank) { + $piggybank->savedSoFar = floatval($piggybank->currentRelevantRep()->currentamount); + $piggybank->percentage = intval($piggybank->savedSoFar / $piggybank->targetamount * 100); + $piggybank->leftToSave = $piggybank->targetamount - $piggybank->savedSoFar; + + /* + * Fill account information: + */ + $account = $piggybank->account; + if (!isset($accounts[$account->id])) { + $accounts[$account->id] = [ + 'name' => $account->name, + 'balance' => $account->balance(), + 'leftForPiggybanks' => $account->balance() - $piggybank->savedSoFar, + 'sumOfSaved' => $piggybank->savedSoFar, + 'sumOfTargets' => floatval($piggybank->targetamount), + 'leftToSave' => $piggybank->leftToSave + ]; + } else { + $accounts[$account->id]['leftForPiggybanks'] -= $piggybank->savedSoFar; + $accounts[$account->id]['sumOfSaved'] += $piggybank->savedSoFar; + $accounts[$account->id]['sumOfTargets'] += floatval($piggybank->targetamount); + $accounts[$account->id]['leftToSave'] += $piggybank->leftToSave; + } + } + return View::make('piggybanks.index', compact('piggybanks','accounts'))->with('title', 'Piggy banks')->with('mainTitleIcon', 'fa-sort-amount-asc'); + + //throw new NotImplementedException; // $countRepeating = $this->_repository->countRepeating(); // $countNonRepeating = $this->_repository->countNonrepeating(); // @@ -415,4 +499,21 @@ class PiggybankController extends BaseController } -} \ No newline at end of file +} + +// /** +// * @param Piggybank $piggyBank +// * +// * @return $this +// */ +// public function addMoney(Piggybank $piggyBank) +// { +// throw new NotImplementedException; +// $what = 'add'; +// $maxAdd = $this->_repository->leftOnAccount($piggyBank->account); +// $maxRemove = null; +// +// return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with( +// 'maxRemove', $maxRemove +// )->with('piggybank', $piggyBank); +// } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Ifaces/PiggybankInterface.php b/app/lib/FireflyIII/Database/Ifaces/PiggybankInterface.php new file mode 100644 index 0000000000..65af492523 --- /dev/null +++ b/app/lib/FireflyIII/Database/Ifaces/PiggybankInterface.php @@ -0,0 +1,19 @@ +balance(); + /** @var \Piggybank $p */ + foreach ($account->piggybanks()->get() as $p) { + $balance -= $p->currentRelevantRep()->currentamount; + } + + return $balance; + + } + + /** + * + */ + public function __construct() + { + $this->setUser(\Auth::user()); + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + // TODO: Implement validate() method. + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + return $this->getUser()->piggybanks()->where('repeats', 0)->get(); + } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 3c10e4b770..fd7b3c62d3 100644 --- a/app/routes.php +++ b/app/routes.php @@ -200,6 +200,8 @@ Route::group( // piggy bank controller Route::get('/piggybanks', ['uses' => 'PiggybankController@index', 'as' => 'piggybanks.index']); + Route::get('/piggybanks/add/{piggybank}', ['uses' => 'PiggybankController@add']); + Route::get('/piggybanks/remove/{piggybank}', ['uses' => 'PiggybankController@remove']); // Route::get('/repeated',['uses' => 'PiggybankController@repeated','as' => 'piggybanks.index.repeated']); // Route::get('/piggybanks/create/piggybank', ['uses' => 'PiggybankController@createPiggybank','as' => 'piggybanks.create.piggybank']); // Route::get('/piggybanks/create/repeated', ['uses' => 'PiggybankController@createRepeated','as' => 'piggybanks.create.repeated']); @@ -280,11 +282,13 @@ Route::group( // piggy bank controller - Route::post('/piggybanks/store/piggybank', ['uses' => 'PiggybankController@storePiggybank', 'as' => 'piggybanks.store.piggybank']); - Route::post('/piggybanks/store/repeated', ['uses' => 'PiggybankController@storeRepeated', 'as' => 'piggybanks.store.repeated']); - Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); - Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); - Route::post('/piggybanks/mod/{piggybank}', ['uses' => 'PiggybankController@modMoney', 'as' => 'piggybanks.modMoney']); + #Route::post('/piggybanks/store/piggybank', ['uses' => 'PiggybankController@storePiggybank', 'as' => 'piggybanks.store.piggybank']); + #Route::post('/piggybanks/store/repeated', ['uses' => 'PiggybankController@storeRepeated', 'as' => 'piggybanks.store.repeated']); + #Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); + #Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); + #Route::post('/piggybanks/mod/{piggybank}', ['uses' => 'PiggybankController@modMoney', 'as' => 'piggybanks.modMoney']); + Route::post('/piggybanks/add/{piggybank}', ['uses' => 'PiggybankController@postAdd', 'as' => 'piggybanks.add']); + Route::post('/piggybanks/remove/{piggybank}', ['uses' => 'PiggybankController@postRemove', 'as' => 'piggybanks.remove']); // preferences controller diff --git a/app/views/partials/menu.blade.php b/app/views/partials/menu.blade.php index eba87f6edb..c294dbefb5 100644 --- a/app/views/partials/menu.blade.php +++ b/app/views/partials/menu.blade.php @@ -179,14 +179,16 @@ Money management @@ -217,15 +219,19 @@
  • Category
  • + {{--
  • Piggy bank
  • + --}}
  • Recurring transaction
  • + {{--
  • Repeated expense
  • + --}} diff --git a/app/views/piggybanks/add.blade.php b/app/views/piggybanks/add.blade.php new file mode 100644 index 0000000000..6b59827d2f --- /dev/null +++ b/app/views/piggybanks/add.blade.php @@ -0,0 +1,24 @@ + +{{Form::token()}} + + \ No newline at end of file diff --git a/app/views/piggybanks/create-piggybank.blade.php b/app/views/piggybanks/create-piggybank.blade.old.php similarity index 100% rename from app/views/piggybanks/create-piggybank.blade.php rename to app/views/piggybanks/create-piggybank.blade.old.php diff --git a/app/views/piggybanks/create-repeated.blade.php b/app/views/piggybanks/create-repeated.blade.old.php similarity index 100% rename from app/views/piggybanks/create-repeated.blade.php rename to app/views/piggybanks/create-repeated.blade.old.php diff --git a/app/views/piggybanks/delete.blade.php b/app/views/piggybanks/delete.blade.old.php similarity index 100% rename from app/views/piggybanks/delete.blade.php rename to app/views/piggybanks/delete.blade.old.php diff --git a/app/views/piggybanks/edit-piggybank.blade.php b/app/views/piggybanks/edit-piggybank.blade.old.php similarity index 100% rename from app/views/piggybanks/edit-piggybank.blade.php rename to app/views/piggybanks/edit-piggybank.blade.old.php diff --git a/app/views/piggybanks/edit-repeated.blade.php b/app/views/piggybanks/edit-repeated.blade.old.php similarity index 100% rename from app/views/piggybanks/edit-repeated.blade.php rename to app/views/piggybanks/edit-repeated.blade.old.php diff --git a/app/views/piggybanks/index.blade.old.php b/app/views/piggybanks/index.blade.old.php new file mode 100644 index 0000000000..981d7bc1a2 --- /dev/null +++ b/app/views/piggybanks/index.blade.old.php @@ -0,0 +1,223 @@ +@extends('layouts.default') +@section('content') + +@if($countNonRepeating > 0) +
    +@foreach($piggybanks as $piggyBank) + @if($piggyBank->repeats == 0) +
    +
    + +
    +
    +
    +
    +
    +

    + {{mf($piggyBank->currentRelevantRep()->currentamount)}} of {{mf($piggyBank->targetamount)}}
    + @if($piggyBank->targetamount-$piggyBank->currentRelevantRep()->currentamount > 0) + {{mf($piggyBank->targetamount-$piggyBank->currentRelevantRep()->currentamount)}} to go. + @endif +

    + +
    + + + @if($accounts[$piggyBank->account_id]['account']->leftOnAccount > 0) + + @endif + @if($piggyBank->currentRelevantRep()->currentamount > 0) + + @endif +
    + + +
    +
    +
    + @endif +@endforeach +
    +
    +
    +   +
    + +
    +
    +
    +@endif +{{-- + + +

    Current piggy banks

    + @if($countNonRepeating == 0) +

    No piggy banks found.

    + @else + @foreach($piggybanks as $piggyBank) + @if($piggyBank->repeats == 0) +

    + + + + + + + + + + + + +
    {{mf($piggyBank->currentRelevantRep()->currentamount)}} +
    +
    + {{$piggyBank->currentRelevantRep()->pct()}}% +
    +
    +
    {{mf($piggyBank->targetamount)}}
    + +
    + @if($accounts[$piggyBank->account_id]['account']->leftOnAccount > 0) + Add money + @endif + @if($piggyBank->currentRelevantRep()->currentamount > 0) + Remove money + @endif +
    +
    +

    + @if(!is_null($piggyBank->targetdate)) + Target date: {{$piggyBank->targetdate->format('M jS, Y')}}
    + @endif + @if(!is_null($piggyBank->reminder)) + Next reminder: TODO + @endif +

    + +
    +
    + + +
    +
    + @endif + @endforeach + @endif + + +
    +
    +

    Current repeated expenses

    + @if($countRepeating == 0) +

    No repeated expenses found.

    + @else + @foreach($piggybanks as $repeated) + @if($repeated->repeats == 1) +

    {{{$repeated->name}}}

    + + + + + + + + + + + + + +
    {{mf($repeated->currentRelevantRep()->currentamount)}} +
    +
    + {{$repeated->currentRelevantRep()->pct()}}% +
    +
    +
    {{mf($repeated->targetamount)}}
    + +
    + @if($accounts[$repeated->account_id]['account']->leftOnAccount > 0) + Add money + @endif + @if($repeated->currentRelevantRep()->currentamount > 0) + Remove money + @endif + +
    +
    + + @if(!is_null($repeated->reminder)) + + Next reminder: TODO + + @endif + + +
    + + +
    +
    + @endif + @endforeach +@endif + +
    +
    + + + + + + + +--}} +
    +
    +

    Account information

    + + + + + + + + + @foreach($accounts as $account) + + + + + + + + @endforeach +
    AccountLeft for piggy banksTotal planned savingsSaved so farLeft to save
    {{{$account['account']->name}}}{{mf($account['left'])}}{{mf($account['tosave'])}}{{mf($account['saved'])}}{{mf($account['tosave']-$account['saved'])}}
    +
    +
    + + + + + + +@stop diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 981d7bc1a2..47a559ff5a 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -1,223 +1,123 @@ @extends('layouts.default') @section('content') - -@if($countNonRepeating > 0)
    -@foreach($piggybanks as $piggyBank) - @if($piggyBank->repeats == 0) -
    -
    - -
    -
    -
    -
    + @foreach($piggybanks as $piggybank) +
    +
    + +
    +
    +
    + {{mf($piggybank->savedSoFar,true)}} +
    +
    +
    +
    percentage == 100) + class="progress-bar progress-bar-success" + @else + class="progress-bar progress-bar-info" + @endif + role="progressbar" aria-valuenow="{{$piggybank->percentage}}" aria-valuemin="0" aria-valuemax="100" style="width: {{$piggybank->percentage}}%;"> + {{$piggybank->percentage}}% +
    +
    +
    +
    + {{mf($piggybank->targetamount,true)}} +
    -

    - {{mf($piggyBank->currentRelevantRep()->currentamount)}} of {{mf($piggyBank->targetamount)}}
    - @if($piggyBank->targetamount-$piggyBank->currentRelevantRep()->currentamount > 0) - {{mf($piggyBank->targetamount-$piggyBank->currentRelevantRep()->currentamount)}} to go. - @endif -

    - -
    - - - @if($accounts[$piggyBank->account_id]['account']->leftOnAccount > 0) - - @endif - @if($piggyBank->currentRelevantRep()->currentamount > 0) - - @endif +
    +
    +
    + @if($piggybank->leftToSave > 0) + + @endif + +
    +
    +
    +
    + + +
    +
    +
    + @if($piggybank->leftToSave > 0) + {{mf($piggybank->leftToSave)}} + @endif +
    - - +
    -
    - @endif -@endforeach -
    + @endforeach +
    -   + Create piggy bank
    -@endif -{{-- - -

    Current piggy banks

    - @if($countNonRepeating == 0) -

    No piggy banks found.

    - @else - @foreach($piggybanks as $piggyBank) - @if($piggyBank->repeats == 0) -

    - - - - - - - - - - - - -
    {{mf($piggyBank->currentRelevantRep()->currentamount)}} -
    -
    - {{$piggyBank->currentRelevantRep()->pct()}}% -
    -
    -
    {{mf($piggyBank->targetamount)}}
    - -
    - @if($accounts[$piggyBank->account_id]['account']->leftOnAccount > 0) - Add money - @endif - @if($piggyBank->currentRelevantRep()->currentamount > 0) - Remove money - @endif -
    -
    -

    - @if(!is_null($piggyBank->targetdate)) - Target date: {{$piggyBank->targetdate->format('M jS, Y')}}
    - @endif - @if(!is_null($piggyBank->reminder)) - Next reminder: TODO - @endif -

    - -
    -
    - - -
    -
    - @endif - @endforeach - @endif -
    -
    -

    Current repeated expenses

    - @if($countRepeating == 0) -

    No repeated expenses found.

    - @else - @foreach($piggybanks as $repeated) - @if($repeated->repeats == 1) -

    {{{$repeated->name}}}

    - - +
    +
    + Account status +
    +
    +
    + + + + + + + + + @foreach($accounts as $id => $info) - - - + + + + + + - - - - - - -
    AccountBalanceLeft for piggy banksSum of piggy banksSaved so farLeft to save
    {{mf($repeated->currentRelevantRep()->currentamount)}} -
    -
    - {{$repeated->currentRelevantRep()->pct()}}% -
    -
    -
    {{mf($repeated->targetamount)}}{{{$info['name']}}}{{mf($info['balance'])}}{{mf($info['leftForPiggybanks'])}}{{mf($info['sumOfTargets'])}}{{mf($info['sumOfSaved'])}}{{mf($info['leftToSave'])}}
    - -
    - @if($accounts[$repeated->account_id]['account']->leftOnAccount > 0) - Add money - @endif - @if($repeated->currentRelevantRep()->currentamount > 0) - Remove money - @endif - -
    -
    - - @if(!is_null($repeated->reminder)) - - Next reminder: TODO - - @endif - - -
    - - -
    -
    - @endif - @endforeach -@endif - -
    -
    - - - - - - - ---}} -
    -
    -

    Account information

    - - - - - - - - - @foreach($accounts as $account) - - - - - - - - @endforeach -
    AccountLeft for piggy banksTotal planned savingsSaved so farLeft to save
    {{{$account['account']->name}}}{{mf($account['left'])}}{{mf($account['tosave'])}}{{mf($account['saved'])}}{{mf($account['tosave']-$account['saved'])}}
    -
    -
    - - - - -
    + + + @stop +@section('scripts') +{{HTML::script('assets/javascript/firefly/piggybanks.js')}} +@stop \ No newline at end of file diff --git a/app/views/piggybanks/modifyAmount.blade.php b/app/views/piggybanks/modifyAmount.blade.old.php similarity index 100% rename from app/views/piggybanks/modifyAmount.blade.php rename to app/views/piggybanks/modifyAmount.blade.old.php diff --git a/app/views/piggybanks/remove.blade.php b/app/views/piggybanks/remove.blade.php new file mode 100644 index 0000000000..7b12cc3eb2 --- /dev/null +++ b/app/views/piggybanks/remove.blade.php @@ -0,0 +1,24 @@ +
    +{{Form::token()}} + +
    \ No newline at end of file diff --git a/app/views/piggybanks/show.blade.php b/app/views/piggybanks/show.blade.old.php similarity index 100% rename from app/views/piggybanks/show.blade.php rename to app/views/piggybanks/show.blade.old.php diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index 49e6508c67..9e3b6f661b 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -1,6 +1,3 @@ -/* -This line is required to be properly triggered by Google. - */ google.setOnLoadCallback(drawChart); diff --git a/public/assets/javascript/firefly/piggybanks.js b/public/assets/javascript/firefly/piggybanks.js new file mode 100644 index 0000000000..8c228d4de1 --- /dev/null +++ b/public/assets/javascript/firefly/piggybanks.js @@ -0,0 +1,19 @@ +$(function () { + $('.addMoney').on('click',addMoney); + $('.removeMoney').on('click',removeMoney); +}); + +function addMoney(e) { + var pigID = parseInt($(e.target).data('id')); + $('#moneyManagementModal').empty().load('piggybanks/add/' + pigID).modal('show'); + + return false; +} + +function removeMoney(e) { + var pigID = parseInt($(e.target).data('id')); + var pigID = parseInt($(e.target).data('id')); + $('#moneyManagementModal').empty().load('piggybanks/remove/' + pigID).modal('show'); + + return false; +} \ No newline at end of file From 2f8b10e82c12999317c0b98636f58c06b140ba9c Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 2 Nov 2014 14:58:12 +0100 Subject: [PATCH 33/57] All kinds of new code, especially for the piggy banks. --- app/config/firefly.php | 30 +++--- app/controllers/AccountController.php | 62 ++++++++---- app/controllers/GoogleChartController.php | 8 +- app/controllers/PiggybankController.php | 96 ++++++++++++++++--- ...4_06_27_163818_create_piggybanks_table.php | 1 + app/lib/Firefly/Form/Form.php | 73 ++++++++------ .../Piggybanks/EloquentPiggybankTrigger.php | 2 +- app/lib/FireflyIII/Database/Account.php | 31 ++++++ app/lib/FireflyIII/Database/AccountType.php | 12 +++ app/lib/FireflyIII/Database/Budget.php | 11 +++ app/lib/FireflyIII/Database/Category.php | 11 +++ app/lib/FireflyIII/Database/Ifaces/CUD.php | 8 ++ app/lib/FireflyIII/Database/Piggybank.php | 94 +++++++++++++++++- app/lib/FireflyIII/Database/Recurring.php | 15 ++- app/lib/FireflyIII/Database/Transaction.php | 21 ++++ .../Database/TransactionCurrency.php | 11 +++ .../Database/TransactionJournal.php | 11 +++ .../FireflyIII/Database/TransactionType.php | 11 +++ app/lib/FireflyIII/Shared/Toolkit/Form.php | 50 ++++++++++ app/models/Piggybank.php | 3 +- app/routes.php | 8 +- app/views/piggybanks/create.blade.php | 93 ++++++++++++++++++ app/views/piggybanks/edit.blade.php | 93 ++++++++++++++++++ app/views/piggybanks/index.blade.php | 8 +- .../javascript/firefly/gcharts.options.js | 1 + public/assets/javascript/firefly/index.js | 1 - 26 files changed, 676 insertions(+), 89 deletions(-) create mode 100644 app/lib/FireflyIII/Shared/Toolkit/Form.php create mode 100644 app/views/piggybanks/create.blade.php create mode 100644 app/views/piggybanks/edit.blade.php diff --git a/app/config/firefly.php b/app/config/firefly.php index 61ccb7820e..bd7db200f6 100644 --- a/app/config/firefly.php +++ b/app/config/firefly.php @@ -2,10 +2,14 @@ use Carbon\Carbon; return [ - 'index_periods' => ['1D', '1W', '1M', '3M', '6M','1Y', 'custom'], - 'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], - 'piggybank_periods' => ['day', 'week', 'month', 'year'], - 'periods_to_text' => [ + 'index_periods' => ['1D', '1W', '1M', '3M', '6M', '1Y', 'custom'], + 'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], + 'piggybank_periods' => [ + 'week' => 'Week', + 'month' => 'Month', + 'year' => 'Year' + ], + 'periods_to_text' => [ 'weekly' => 'A week', 'monthly' => 'A month', 'quarterly' => 'A quarter', @@ -13,7 +17,7 @@ return [ 'yearly' => 'A year', ], - 'range_to_text' => [ + 'range_to_text' => [ '1D' => 'day', '1W' => 'week', '1M' => 'month', @@ -21,15 +25,15 @@ return [ '6M' => 'half year', 'custom' => '(custom)' ], - 'range_to_name' => [ - '1D' => 'one day', - '1W' => 'one week', - '1M' => 'one month', - '3M' => 'three months', - '6M' => 'six months', - '1Y' => 'one year', + 'range_to_name' => [ + '1D' => 'one day', + '1W' => 'one week', + '1M' => 'one month', + '3M' => 'three months', + '6M' => 'six months', + '1Y' => 'one year', ], - 'range_to_repeat_freq' => [ + 'range_to_repeat_freq' => [ '1D' => 'weekly', '1W' => 'weekly', '1M' => 'monthly', diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 8b3b4d4795..0b938206c0 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -340,28 +340,50 @@ class AccountController extends BaseController */ public function update(Account $account) { - /** @var \Account $account */ - $account = $this->_repository->update($account, Input::all()); - if ($account->validate()) { - Session::flash('success', 'Account "' . $account->name . '" updated.'); - switch ($account->accountType->type) { - case 'Asset account': - case 'Default account': - return Redirect::route('accounts.asset'); - break; - case 'Expense account': - case 'Beneficiary account': - return Redirect::route('accounts.expense'); - break; - case 'Revenue account': - return Redirect::route('accounts.revenue'); - break; - } - } else { - Session::flash('error', 'Could not update account: ' . $account->errors()->first()); + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + $data = Input::except('_token'); - return Redirect::route('accounts.edit', $account->id)->withInput()->withErrors($account->errors()); + switch($account->accountType->type) { + default: + throw new FireflyException('Cannot handle account type "' . e($account->accountType->type) . '"'); + break; + case 'Default account': + $data['what'] = 'asset'; + break; + } + + switch (Input::get('post_submit_action')) { + default: + throw new FireflyException('Cannot handle post_submit_action "' . e(Input::get('post_submit_action')) . '"'); + break; + case 'create_another': + case 'store': + $messages = $acct->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save account: ' . $messages['errors']->first()); + return Redirect::route('accounts.create', $data['what'])->withInput()->withErrors($messages['errors']); + } + // store! + $acct->update($account, $data); + Session::flash('success', 'Account updated!'); + + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('accounts.edit', $account->id); + } else { + return Redirect::route('accounts.edit', $account->id); + } + case 'validate_only': + $messageBags = $acct->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); + return Redirect::route('accounts.edit', $account->id)->withInput(); + break; } } diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index 8d08b8d3e7..ede430a25b 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -43,11 +43,11 @@ class GoogleChartController extends BaseController $row = [clone $current]; foreach ($accounts as $account) { - if ($current > Carbon::now()) { - $row[] = null; - } else { + //if ($current > Carbon::now()) { + // $row[] = 0; + //} else { $row[] = $account->balance($current); - } + //} } diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 798237af50..311f060ab9 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -3,6 +3,7 @@ use Firefly\Exception\FireflyException; use FireflyIII\Exception\NotImplementedException; use Illuminate\Support\Collection; +use Illuminate\Support\MessageBag; /** * Class PiggybankController @@ -23,7 +24,19 @@ class PiggybankController extends BaseController */ public function create() { - throw new NotImplementedException; + + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Shared\Toolkit\Form $toolkit */ + $toolkit = App::make('FireflyIII\Shared\Toolkit\Form'); + + $periods = Config::get('firefly.piggybank_periods'); + + + $accounts = $toolkit->makeSelectList($acct->getAssetAccounts()); + return View::make('piggybanks.create', compact('accounts', 'periods'))->with('title', 'Piggy banks')->with('mainTitleIcon', 'fa-sort-amount-asc') + ->with('subTitle', 'Create new piggy bank')->with('subTitleIcon', 'fa-plus'); } @@ -96,9 +109,36 @@ class PiggybankController extends BaseController * * @return $this */ - public function edit(Piggybank $piggyBank) + public function edit(Piggybank $piggybank) { - throw new NotImplementedException; + + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Shared\Toolkit\Form $toolkit */ + $toolkit = App::make('FireflyIII\Shared\Toolkit\Form'); + + $periods = Config::get('firefly.piggybank_periods'); + + $accounts = $toolkit->makeSelectList($acct->getAssetAccounts()); + + /* + * Flash some data to fill the form. + */ + $prefilled = [ + 'name' => $piggybank->name, + 'account_id' => $piggybank->account_id, + 'targetamount' => $piggybank->targetamount, + 'targetdate' => $piggybank->targetdate, + 'remind_me' => intval($piggybank->remind_me) == 1 ? true : false + ]; + Session::flash('prefilled', $prefilled); + + return View::make('piggybanks.edit', compact('piggybank', 'accounts', 'periods','prefilled'))->with('title', 'Piggybanks')->with( + 'mainTitleIcon', 'fa-sort-amount-asc' + ) + ->with('subTitle', 'Edit piggy bank "' . e($piggybank->name) . '"')->with('subTitleIcon', 'fa-pencil'); + //throw new NotImplementedException; // /** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */ // $toolkit = App::make('Firefly\Helper\Toolkit\Toolkit'); // @@ -258,9 +298,6 @@ class PiggybankController extends BaseController /** @var \FireflyIII\Database\Piggybank $repos */ $repos = App::make('FireflyIII\Database\Piggybank'); - /** @var \FireflyIII\Database\Account $acct */ - $acct = App::make('FireflyIII\Database\Account'); - /** @var Collection $piggybanks */ $piggybanks = $repos->get(); @@ -291,7 +328,7 @@ class PiggybankController extends BaseController $accounts[$account->id]['leftToSave'] += $piggybank->leftToSave; } } - return View::make('piggybanks.index', compact('piggybanks','accounts'))->with('title', 'Piggy banks')->with('mainTitleIcon', 'fa-sort-amount-asc'); + return View::make('piggybanks.index', compact('piggybanks', 'accounts'))->with('title', 'Piggy banks')->with('mainTitleIcon', 'fa-sort-amount-asc'); //throw new NotImplementedException; // $countRepeating = $this->_repository->countRepeating(); @@ -410,12 +447,49 @@ class PiggybankController extends BaseController // ->with('balance', $balance); } -// /** -// * @return $this|\Illuminate\Http\RedirectResponse -// */ + /** + * + */ public function store() { - throw new NotImplementedException; + $data = Input::all(); + $data['repeats'] = 0; + /** @var \FireflyIII\Database\Piggybank $repos */ + $repos = App::make('FireflyIII\Database\Piggybank'); + + switch ($data['post_submit_action']) { + default: + throw new FireflyException('Cannot handle post_submit_action "' . e($data['post_submit_action']) . '"'); + break; + case 'create_another': + case 'store': + $messages = $repos->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save piggy bank: ' . $messages['errors']->first()); + return Redirect::route('piggybanks.create')->withInput()->withErrors($messages['errors']); + } + // store! + $repos->store($data); + Session::flash('success', 'New piggy bank stored!'); + + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('piggybanks.create'); + } else { + return Redirect::route('piggybanks.index'); + } + break; + case 'validate_only': + $messageBags = $repos->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); + + return Redirect::route('piggybanks.create')->withInput(); + break; + } // $data = Input::all(); // unset($data['_token']); // diff --git a/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php b/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php index b30cae3406..74ff3e20c8 100644 --- a/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php +++ b/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php @@ -33,6 +33,7 @@ class CreatePiggybanksTable extends Migration $table->smallInteger('rep_times')->unsigned()->nullable(); $table->enum('reminder', ['day', 'week', 'month', 'year'])->nullable(); $table->smallInteger('reminder_skip')->unsigned(); + $table->boolean('remind_me'); $table->integer('order')->unsigned(); // connect account to piggybank. diff --git a/app/lib/Firefly/Form/Form.php b/app/lib/Firefly/Form/Form.php index 1b697f01fe..a06c3184ec 100644 --- a/app/lib/Firefly/Form/Form.php +++ b/app/lib/Firefly/Form/Form.php @@ -10,9 +10,10 @@ class Form { /** - * @param $name - * @param null $value + * @param $name + * @param null $value * @param array $options + * * @return string * @throws FireflyException */ @@ -40,7 +41,7 @@ class Form public static function ffAmount($name, $value = null, array $options = []) { $options['step'] = 'any'; - $options['min'] = '0.01'; + $options['min'] = '0.01'; return self::ffInput('amount', $name, $value, $options); } @@ -61,9 +62,10 @@ class Form } /** - * @param $name - * @param null $value + * @param $name + * @param null $value * @param array $options + * * @return string * @throws FireflyException */ @@ -73,9 +75,10 @@ class Form } /** - * @param $name - * @param null $value + * @param $name + * @param null $value * @param array $options + * * @return string * @throws FireflyException */ @@ -86,10 +89,11 @@ class Form } /** - * @param $name + * @param $name * @param array $list - * @param null $selected + * @param null $selected * @param array $options + * * @return string * @throws FireflyException */ @@ -99,9 +103,10 @@ class Form } /** - * @param $name - * @param null $value + * @param $name + * @param null $value * @param array $options + * * @return string * @throws FireflyException */ @@ -111,16 +116,25 @@ class Form } - public static function label($name) + /** + * @param $name + * @param $options + * + * @return string + */ + public static function label($name, $options) { + if (isset($options['label'])) { + return $options['label']; + } $labels = [ - 'amount_min' => 'Amount (min)', - 'amount_max' => 'Amount (max)', - 'match' => 'Matches on', - 'repeat_freq' => 'Repetition', + 'amount_min' => 'Amount (min)', + 'amount_max' => 'Amount (max)', + 'match' => 'Matches on', + 'repeat_freq' => 'Repetition', 'account_from_id' => 'Account from', - 'account_to_id' => 'Account to', - 'account_id' => 'Asset account' + 'account_to_id' => 'Account to', + 'account_id' => 'Asset account' ]; return isset($labels[$name]) ? $labels[$name] : str_replace('_', ' ', ucfirst($name)); @@ -174,28 +188,29 @@ class Form case 'create': $return = '
    '; break; case 'update': $return = '
    '; break; default: throw new FireflyException('Cannot create ffOptionsList for option (store+return) ' . $type); break; } - return $store.$validate.$return; + return $store . $validate . $return; } /** - * @param $type - * @param $name - * @param null $value + * @param $type + * @param $name + * @param null $value * @param array $options * @param array $list + * * @return string * @throws FireflyException */ @@ -204,10 +219,10 @@ class Form /* * add some defaults to this method: */ - $options['class'] = 'form-control'; - $options['id'] = 'ffInput_' . $name; + $options['class'] = 'form-control'; + $options['id'] = 'ffInput_' . $name; $options['autocomplete'] = 'off'; - $label = self::label($name); + $label = self::label($name, $options); /* * Make label and placeholder look nice. */ @@ -216,9 +231,9 @@ class Form /* * Get prefilled value: */ - if(\Session::has('prefilled')) { + if (\Session::has('prefilled')) { $prefilled = \Session::get('prefilled'); - $value = isset($prefilled[$name]) && is_null($value) ? $prefilled[$name] : $value; + $value = isset($prefilled[$name]) && is_null($value) ? $prefilled[$name] : $value; } /* diff --git a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php index 80023eb48e..09e3943099 100644 --- a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php +++ b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php @@ -193,7 +193,7 @@ class EloquentPiggybankTrigger 'Firefly\Trigger\Piggybanks\EloquentPiggybankTrigger@updateRelatedTransfer' ); $events->listen( - 'piggybanks.check', 'Firefly\Trigger\Piggybanks\EloquentPiggybankTrigger@checkRepeatingPiggies' + 'piggybanks.storepiggybanks.check', 'Firefly\Trigger\Piggybanks\EloquentPiggybankTrigger@checkRepeatingPiggies' ); } diff --git a/app/lib/FireflyIII/Database/Account.php b/app/lib/FireflyIII/Database/Account.php index 3f42aaecfb..d683bf9b2c 100644 --- a/app/lib/FireflyIII/Database/Account.php +++ b/app/lib/FireflyIII/Database/Account.php @@ -27,6 +27,37 @@ class Account implements CUD, CommonDatabaseCalls, AccountInterface $this->setUser(\Auth::user()); } + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + $model->name = $data['name']; + $model->active = isset($data['active']) ? intval($data['active']) : 0; + $model->save(); + + if (isset($data['openingbalance']) && isset($data['openingbalancedate'])) { + $openingBalance = $this->openingBalanceTransaction($model); + + $openingBalance->date = new Carbon($data['openingbalancedate']); + $openingBalance->save(); + $amount = floatval($data['openingbalance']); + /** @var \Transaction $transaction */ + foreach ($openingBalance->transactions as $transaction) { + if ($transaction->account_id == $model->id) { + $transaction->amount = $amount; + } else { + $transaction->amount = $amount * -1; + } + $transaction->save(); + } + } + return true; + } + /** * Get all asset accounts. Optional JSON based parameters. * diff --git a/app/lib/FireflyIII/Database/AccountType.php b/app/lib/FireflyIII/Database/AccountType.php index 7597174d4f..a08b00f385 100644 --- a/app/lib/FireflyIII/Database/AccountType.php +++ b/app/lib/FireflyIII/Database/AccountType.php @@ -24,6 +24,7 @@ class AccountType implements AccountTypeInterface, CUD, CommonDatabaseCalls * @param $what * * @return \AccountType|null + * @throws FireflyException */ public function findByWhat($what) { @@ -125,4 +126,15 @@ class AccountType implements AccountTypeInterface, CUD, CommonDatabaseCalls { // TODO: Implement getByIds() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Budget.php b/app/lib/FireflyIII/Database/Budget.php index f3a9f62d2d..86d89d65c9 100644 --- a/app/lib/FireflyIII/Database/Budget.php +++ b/app/lib/FireflyIII/Database/Budget.php @@ -154,4 +154,15 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface } )->before($end)->after($start)->lessThan(0)->transactionTypes(['Withdrawal'])->get(); } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Category.php b/app/lib/FireflyIII/Database/Category.php index 7ab7112268..47349ac021 100644 --- a/app/lib/FireflyIII/Database/Category.php +++ b/app/lib/FireflyIII/Database/Category.php @@ -115,4 +115,15 @@ class Category implements CUD, CommonDatabaseCalls, CategoryInterface { // TODO: Implement findByWhat() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Ifaces/CUD.php b/app/lib/FireflyIII/Database/Ifaces/CUD.php index b193394134..3c457f8629 100644 --- a/app/lib/FireflyIII/Database/Ifaces/CUD.php +++ b/app/lib/FireflyIII/Database/Ifaces/CUD.php @@ -46,4 +46,12 @@ interface CUD */ public function store(array $data); + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data); + } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Piggybank.php b/app/lib/FireflyIII/Database/Piggybank.php index 014ccceaa1..49acffcf68 100644 --- a/app/lib/FireflyIII/Database/Piggybank.php +++ b/app/lib/FireflyIII/Database/Piggybank.php @@ -2,6 +2,7 @@ namespace FireflyIII\Database; use Carbon\Carbon; +use FireflyIII\Exception\NotImplementedException; use Illuminate\Support\MessageBag; use LaravelBook\Ardent\Ardent; use Illuminate\Support\Collection; @@ -76,7 +77,81 @@ class Piggybank implements CUD, CommonDatabaseCalls, PiggybankInterface */ public function validate(array $model) { - // TODO: Implement validate() method. + $warnings = new MessageBag; + $successes = new MessageBag; + $errors = new MessageBag; + + /* + * Name validation: + */ + if (!isset($model['name'])) { + $errors->add('name', 'Name is mandatory'); + } + + if (isset($model['name']) && strlen($model['name']) == 0) { + $errors->add('name', 'Name is too short'); + } + if (isset($model['name']) && strlen($model['name']) > 100) { + $errors->add('name', 'Name is too long'); + } + + if (intval($model['account_id']) == 0) { + $errors->add('account_id', 'Account is mandatory'); + } + if ($model['targetdate'] == '' && isset($model['remind_me']) && intval($model['remind_me']) == 1) { + $errors->add('targetdate', 'Target date is mandatory when setting reminders.'); + } + if ($model['targetdate'] != '') { + try { + new Carbon($model['targetdate']); + } catch (\Exception $e) { + $errors->add('date', 'Invalid date.'); + } + } + if (floatval($model['targetamount']) < 0.01) { + $errors->add('targetamount', 'Amount should be above 0.01.'); + } + if (!in_array(ucfirst($model['reminder']), \Config::get('firefly.piggybank_periods'))) { + $errors->add('reminder', 'Invalid reminder period (' . $model['reminder'] . ')'); + } + // check period. + if (!$errors->has('reminder') && !$errors->has('targetdate') && isset($model['remind_me']) && intval($model['remind_me']) == 1) { + $today = new Carbon; + $target = new Carbon($model['targetdate']); + switch ($model['reminder']) { + case 'week': + $today->addWeek(); + break; + case 'month': + $today->addMonth(); + break; + case 'year': + $today->addYear(); + break; + } + if ($today > $target) { + $errors->add('reminder', 'Target date is too close to today to set reminders.'); + } + } + + $validator = \Validator::make($model, \Piggybank::$rules); + if ($validator->invalid()) { + $errors->merge($errors); + } + + // add ok messages. + $list = ['name', 'account_id', 'targetamount', 'targetdate', 'remind_me', 'reminder']; + foreach ($list as $entry) { + if (!$errors->has($entry) && !$warnings->has($entry)) { + $successes->add($entry, 'OK'); + } + } + + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; } /** @@ -86,7 +161,22 @@ class Piggybank implements CUD, CommonDatabaseCalls, PiggybankInterface */ public function store(array $data) { - // TODO: Implement store() method. + $data['rep_every'] = isset($data['rep_every']) ? $data['rep_every'] : 0; + $data['reminder_skip'] = isset($data['reminder_skip']) ? $data['reminder_skip'] : 0; + $data['order'] = isset($data['order']) ? $data['order'] : 0; + $data['remind_me'] = isset($data['remind_me']) ? intval($data['remind_me']) : 0; + $data['startdate'] = isset($data['startdate']) ? $data['startdate'] : Carbon::now()->format('Y-m-d'); + $data['targetdate'] = isset($data['targetdate']) && $data['targetdate'] != '' ? $data['targetdate'] : null; + + + $piggybank = new \Piggybank($data); + if (!$piggybank->validate()) { + var_dump($piggybank->errors()->all()); + exit; + } + $piggybank->save(); + \Event::fire('piggybanks.store', [$piggybank]); + $piggybank->save(); } /** diff --git a/app/lib/FireflyIII/Database/Recurring.php b/app/lib/FireflyIII/Database/Recurring.php index d60b75c222..b8cb6ba29f 100644 --- a/app/lib/FireflyIII/Database/Recurring.php +++ b/app/lib/FireflyIII/Database/Recurring.php @@ -29,8 +29,8 @@ class Recurring implements CUD, CommonDatabaseCalls, RecurringInterface /** * @param \RecurringTransaction $recurring - * @param Carbon $current - * @param Carbon $currentEnd + * @param Carbon $start + * @param Carbon $end * * @return \TransactionJournal|null */ @@ -129,4 +129,15 @@ class Recurring implements CUD, CommonDatabaseCalls, RecurringInterface { // TODO: Implement findByWhat() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Transaction.php b/app/lib/FireflyIII/Database/Transaction.php index 5a64debea1..744880dd73 100644 --- a/app/lib/FireflyIII/Database/Transaction.php +++ b/app/lib/FireflyIII/Database/Transaction.php @@ -171,4 +171,25 @@ class Transaction implements TransactionInterface, CUD, CommonDatabaseCalls { // TODO: Implement findByWhat() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } + + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids) + { + // TODO: Implement getByIds() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionCurrency.php b/app/lib/FireflyIII/Database/TransactionCurrency.php index 912b26a07c..9e46efb207 100644 --- a/app/lib/FireflyIII/Database/TransactionCurrency.php +++ b/app/lib/FireflyIII/Database/TransactionCurrency.php @@ -116,4 +116,15 @@ class TransactionCurrency implements TransactionCurrencyInterface, CUD, CommonDa { // TODO: Implement getByIds() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal.php index a0dcacff8c..e6d0d5a12f 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal.php @@ -267,4 +267,15 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData { // TODO: Implement getByIds() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionType.php b/app/lib/FireflyIII/Database/TransactionType.php index 54c56831b9..0022ec36fb 100644 --- a/app/lib/FireflyIII/Database/TransactionType.php +++ b/app/lib/FireflyIII/Database/TransactionType.php @@ -115,4 +115,15 @@ class TransactionType implements TransactionTypeInterface, CUD, CommonDatabaseCa { // TODO: Implement getByIds() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Shared/Toolkit/Form.php b/app/lib/FireflyIII/Shared/Toolkit/Form.php new file mode 100644 index 0000000000..7383a0e7a2 --- /dev/null +++ b/app/lib/FireflyIII/Shared/Toolkit/Form.php @@ -0,0 +1,50 @@ +id); + $title = null; + if (is_null($titleField)) { + // try 'title' field. + if (isset($entry->title)) { + $title = $entry->title; + } + // try 'name' field + if (is_null($title)) { + $title = $entry->name; + } + + // try 'description' field + if (is_null($title)) { + $title = $entry->description; + } + } else { + $title = $entry->$titleField; + } + $selectList[$id] = $title; + } + return $selectList; + } + +} \ No newline at end of file diff --git a/app/models/Piggybank.php b/app/models/Piggybank.php index b754e133f2..143f900543 100644 --- a/app/models/Piggybank.php +++ b/app/models/Piggybank.php @@ -56,6 +56,7 @@ class Piggybank extends Ardent 'rep_times' => 'min:1|max:100', // how many times do you want to save this amount? eg. 3 times 'reminder' => 'in:day,week,month,year', // want a reminder to put money in this? 'reminder_skip' => 'required|min:0|max:100', // every week? every 2 months? + 'remind_me' => 'required|boolean', 'order' => 'required:min:1', // not yet used. ]; public $fillable @@ -71,6 +72,7 @@ class Piggybank extends Ardent 'rep_times', 'reminder', 'reminder_skip', + 'remind_me', 'order' ]; @@ -90,7 +92,6 @@ class Piggybank extends Ardent $rep->targetdate = $target; $rep->currentamount = 0; $rep->save(); - \Event::fire('piggybanks.repetition', [$rep]); return $rep; diff --git a/app/routes.php b/app/routes.php index fd7b3c62d3..5db5c8183b 100644 --- a/app/routes.php +++ b/app/routes.php @@ -202,13 +202,15 @@ Route::group( Route::get('/piggybanks', ['uses' => 'PiggybankController@index', 'as' => 'piggybanks.index']); Route::get('/piggybanks/add/{piggybank}', ['uses' => 'PiggybankController@add']); Route::get('/piggybanks/remove/{piggybank}', ['uses' => 'PiggybankController@remove']); + Route::get('/piggybanks/edit/{piggybank}', ['uses' => 'PiggybankController@edit','as' => 'piggybanks.edit']); + Route::get('/piggybanks/create', ['uses' => 'PiggybankController@create', 'as' => 'piggybanks.create']); + + // Route::get('/repeated',['uses' => 'PiggybankController@repeated','as' => 'piggybanks.index.repeated']); -// Route::get('/piggybanks/create/piggybank', ['uses' => 'PiggybankController@createPiggybank','as' => 'piggybanks.create.piggybank']); // Route::get('/piggybanks/create/repeated', ['uses' => 'PiggybankController@createRepeated','as' => 'piggybanks.create.repeated']); // Route::get('/piggybanks/addMoney/{piggybank}', ['uses' => 'PiggybankController@addMoney','as' => 'piggybanks.amount.add']); // Route::get('/piggybanks/removeMoney/{piggybank}', ['uses' => 'PiggybankController@removeMoney','as' => 'piggybanks.amount.remove']); // Route::get('/piggybanks/show/{piggybank}', ['uses' => 'PiggybankController@show','as' => 'piggybanks.show']); -// Route::get('/piggybanks/edit/{piggybank}', ['uses' => 'PiggybankController@edit','as' => 'piggybanks.edit']); // Route::get('/piggybanks/delete/{piggybank}', ['uses' => 'PiggybankController@delete','as' => 'piggybanks.delete']); // Route::post('/piggybanks/updateAmount/{piggybank}',['uses' => 'PiggybankController@updateAmount','as' => 'piggybanks.updateAmount']); @@ -282,7 +284,7 @@ Route::group( // piggy bank controller - #Route::post('/piggybanks/store/piggybank', ['uses' => 'PiggybankController@storePiggybank', 'as' => 'piggybanks.store.piggybank']); + Route::post('/piggybanks/store', ['uses' => 'PiggybankController@store', 'as' => 'piggybanks.store']); #Route::post('/piggybanks/store/repeated', ['uses' => 'PiggybankController@storeRepeated', 'as' => 'piggybanks.store.repeated']); #Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); #Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); diff --git a/app/views/piggybanks/create.blade.php b/app/views/piggybanks/create.blade.php new file mode 100644 index 0000000000..6c05dcc7fe --- /dev/null +++ b/app/views/piggybanks/create.blade.php @@ -0,0 +1,93 @@ +@extends('layouts.default') +@section('content') +{{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.store')])}} + +
    +
    +
    +
    + Mandatory fields +
    +
    + {{Form::ffText('name')}} + {{Form::ffSelect('account_id',$accounts,null,['label' => 'Save on account'])}} + {{Form::ffAmount('targetamount')}} + +
    +
    +

    + +

    +
    +
    + +
    +
    + Optional fields +
    +
    + {{Form::ffDate('targetdate')}} + {{Form::ffCheckbox('remind_me','1',false,['label' => 'Remind me'])}} + {{Form::ffSelect('reminder',$periods,'month',['label' => 'Remind every'])}} +
    +
    + + +
    +
    + Options +
    +
    + {{Form::ffOptionsList('create','piggy bank')}} +
    +
    + +
    +
    +{{-- + +

    Mandatory fields

    + +

    Optional fields

    + + +
    + {{ Form::label('reminder', 'Remind you every', ['class' => 'col-sm-4 control-label'])}} +
    + + + + @if($errors->has('reminder')) +

    {{$errors->first('reminder')}}

    + @else + Enter a number and a period and Firefly will remind you to add money + to this piggy bank every now and then. + @endif +
    +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +--}} + +{{Form::close()}} +@stop diff --git a/app/views/piggybanks/edit.blade.php b/app/views/piggybanks/edit.blade.php new file mode 100644 index 0000000000..69169a5279 --- /dev/null +++ b/app/views/piggybanks/edit.blade.php @@ -0,0 +1,93 @@ +@extends('layouts.default') +@section('content') +{{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.store')])}} + +
    +
    +
    +
    + Mandatory fields +
    +
    + {{Form::ffText('name')}} + {{Form::ffSelect('account_id',$accounts,null,['label' => 'Save on account'])}} + {{Form::ffAmount('targetamount')}} + +
    +
    +

    + +

    +
    +
    + +
    +
    + Optional fields +
    +
    + {{Form::ffDate('targetdate')}} + {{Form::ffCheckbox('remind_me','1',$prefilled['remind_me'],['label' => 'Remind me'])}} + {{Form::ffSelect('reminder',$periods,'month',['label' => 'Remind every'])}} +
    +
    + + +
    +
    + Options +
    +
    + {{Form::ffOptionsList('create','piggy bank')}} +
    +
    + +
    +
    +{{-- + +

    Mandatory fields

    + +

    Optional fields

    + + +
    + {{ Form::label('reminder', 'Remind you every', ['class' => 'col-sm-4 control-label'])}} +
    + + + + @if($errors->has('reminder')) +

    {{$errors->first('reminder')}}

    + @else + Enter a number and a period and Firefly will remind you to add money + to this piggy bank every now and then. + @endif +
    +
    + + + + + +
    +
    + +
    +
    + +
    +
    +
    +
    +--}} + +{{Form::close()}} +@stop diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 47a559ff5a..3907dbf830 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -40,7 +40,7 @@
    - +
    @@ -60,7 +60,11 @@ Create piggy bank diff --git a/public/assets/javascript/firefly/gcharts.options.js b/public/assets/javascript/firefly/gcharts.options.js index 75064974fc..7cc923c261 100644 --- a/public/assets/javascript/firefly/gcharts.options.js +++ b/public/assets/javascript/firefly/gcharts.options.js @@ -3,6 +3,7 @@ var defaultLineChartOptions = { legend: { position: 'none' }, + interpolateNulls: true, lineWidth: 1, chartArea: { left: 50, diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index 9e3b6f661b..edd171aec6 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -2,7 +2,6 @@ google.setOnLoadCallback(drawChart); function drawChart() { - console.log(1); googleLineChart('chart/home/account', 'accounts-chart'); googleBarChart('chart/home/budgets','budgets-chart'); googleColumnChart('chart/home/categories','categories-chart'); From 03aac2f7441990dca946be5cdf84fc9f6822ebe2 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 2 Nov 2014 14:59:09 +0100 Subject: [PATCH 34/57] Implemented method stub. --- app/lib/FireflyIII/Database/Piggybank.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/lib/FireflyIII/Database/Piggybank.php b/app/lib/FireflyIII/Database/Piggybank.php index 49acffcf68..bf34975eef 100644 --- a/app/lib/FireflyIII/Database/Piggybank.php +++ b/app/lib/FireflyIII/Database/Piggybank.php @@ -222,4 +222,15 @@ class Piggybank implements CUD, CommonDatabaseCalls, PiggybankInterface { // TODO: Implement findByWhat() method. } + + /** + * @param Ardent $model + * @param array $data + * + * @return bool + */ + public function update(Ardent $model, array $data) + { + // TODO: Implement update() method. + } } \ No newline at end of file From 0f1437dd6a743be48df73a56059639ea0a5de35c Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 2 Nov 2014 16:47:01 +0100 Subject: [PATCH 35/57] Delete piggy banks. --- app/controllers/PiggybankController.php | 42 ++++++++--------------- app/lib/FireflyIII/Database/Piggybank.php | 2 +- app/routes.php | 3 +- app/views/piggybanks/delete.blade.php | 36 +++++++++++++++++++ app/views/piggybanks/index.blade.php | 2 +- 5 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 app/views/piggybanks/delete.blade.php diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 311f060ab9..fd1c871238 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -66,19 +66,13 @@ class PiggybankController extends BaseController * * @return $this */ - public function delete(Piggybank $piggyBank) + public function delete(Piggybank $piggybank) { - throw new NotImplementedException; -// View::share('subTitle', 'Delete "' . $piggyBank->name . '"'); -// if ($piggyBank->repeats == 1) { -// View::share('title', 'Repeated expenses'); -// View::share('mainTitleIcon', 'fa-rotate-right'); -// } else { -// View::share('title', 'Piggy banks'); -// View::share('mainTitleIcon', 'fa-sort-amount-asc'); -// } -// -// return View::make('piggybanks.delete')->with('piggybank', $piggyBank); + View::share('subTitle', 'Delete "' . $piggybank->name . '"'); + View::share('title', 'Piggy banks'); + View::share('mainTitleIcon', 'fa-sort-amount-asc'); + + return View::make('piggybanks.delete')->with('piggybank', $piggybank); } /** @@ -88,20 +82,14 @@ class PiggybankController extends BaseController */ public function destroy(Piggybank $piggyBank) { - throw new NotImplementedException; -// Event::fire('piggybanks.destroy', [$piggyBank]); -// if ($piggyBank->repeats == 1) { -// $route = 'piggybanks.index.repeated'; -// $message = 'Repeated expense'; -// } else { -// $route = 'piggybanks.index.piggybanks'; -// $message = 'Piggybank'; -// } -// $this->_repository->destroy($piggyBank); -// -// Session::flash('success', $message . ' deleted.'); -// -// return Redirect::route($route); + Event::fire('piggybanks.destroy', [$piggyBank]); + + /** @var \FireflyIII\Database\Piggybank $acct */ + $repos = App::make('FireflyIII\Database\Piggybank'); + $repos->destroy($piggyBank); + Session::flash('success', 'Piggy bank deleted.'); + + return Redirect::route('piggybanks.index'); } /** @@ -134,7 +122,7 @@ class PiggybankController extends BaseController ]; Session::flash('prefilled', $prefilled); - return View::make('piggybanks.edit', compact('piggybank', 'accounts', 'periods','prefilled'))->with('title', 'Piggybanks')->with( + return View::make('piggybanks.edit', compact('piggybank', 'accounts', 'periods', 'prefilled'))->with('title', 'Piggybanks')->with( 'mainTitleIcon', 'fa-sort-amount-asc' ) ->with('subTitle', 'Edit piggy bank "' . e($piggybank->name) . '"')->with('subTitleIcon', 'fa-pencil'); diff --git a/app/lib/FireflyIII/Database/Piggybank.php b/app/lib/FireflyIII/Database/Piggybank.php index bf34975eef..9f34185387 100644 --- a/app/lib/FireflyIII/Database/Piggybank.php +++ b/app/lib/FireflyIII/Database/Piggybank.php @@ -51,7 +51,7 @@ class Piggybank implements CUD, CommonDatabaseCalls, PiggybankInterface */ public function destroy(Ardent $model) { - // TODO: Implement destroy() method. + $model->delete(); } /** diff --git a/app/routes.php b/app/routes.php index 5db5c8183b..329edfa3d1 100644 --- a/app/routes.php +++ b/app/routes.php @@ -204,6 +204,7 @@ Route::group( Route::get('/piggybanks/remove/{piggybank}', ['uses' => 'PiggybankController@remove']); Route::get('/piggybanks/edit/{piggybank}', ['uses' => 'PiggybankController@edit','as' => 'piggybanks.edit']); Route::get('/piggybanks/create', ['uses' => 'PiggybankController@create', 'as' => 'piggybanks.create']); + Route::get('/piggybanks/delete/{piggybank}', ['uses' => 'PiggybankController@delete','as' => 'piggybanks.delete']); // Route::get('/repeated',['uses' => 'PiggybankController@repeated','as' => 'piggybanks.index.repeated']); @@ -287,7 +288,7 @@ Route::group( Route::post('/piggybanks/store', ['uses' => 'PiggybankController@store', 'as' => 'piggybanks.store']); #Route::post('/piggybanks/store/repeated', ['uses' => 'PiggybankController@storeRepeated', 'as' => 'piggybanks.store.repeated']); #Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); - #Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); + Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); #Route::post('/piggybanks/mod/{piggybank}', ['uses' => 'PiggybankController@modMoney', 'as' => 'piggybanks.modMoney']); Route::post('/piggybanks/add/{piggybank}', ['uses' => 'PiggybankController@postAdd', 'as' => 'piggybanks.add']); Route::post('/piggybanks/remove/{piggybank}', ['uses' => 'PiggybankController@postRemove', 'as' => 'piggybanks.remove']); diff --git a/app/views/piggybanks/delete.blade.php b/app/views/piggybanks/delete.blade.php new file mode 100644 index 0000000000..52695c20b0 --- /dev/null +++ b/app/views/piggybanks/delete.blade.php @@ -0,0 +1,36 @@ +@extends('layouts.default') +@section('content') +{{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.destroy',$piggybank->id)])}} +
    +
    +
    +
    + Delete piggy bank "{{{$piggybank->name}}}" +
    +
    +

    + Are you sure? +

    + +

    + + Cancel +

    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + +{{Form::close()}} +@stop \ No newline at end of file diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 3907dbf830..3ba8bf53a4 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -41,7 +41,7 @@
    - +
    From ef39f31ea10ee037f1c01b3ac6fb505d551477bd Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 2 Nov 2014 18:46:01 +0100 Subject: [PATCH 36/57] First reports. --- app/controllers/AccountController.php | 8 +- app/controllers/GoogleChartController.php | 84 +++++- app/controllers/ReportController.php | 51 +++- .../Ifaces/TransactionJournalInterface.php | 20 ++ .../Database/TransactionJournal.php | 46 +++ app/models/TransactionJournal.php | 284 ++---------------- app/routes.php | 3 + app/views/reports/index.blade.php | 17 +- app/views/reports/year.blade.php | 84 ++++++ public/assets/javascript/firefly/index.js | 52 ++-- public/assets/javascript/firefly/reports.js | 7 + 11 files changed, 370 insertions(+), 286 deletions(-) create mode 100644 app/views/reports/year.blade.php create mode 100644 public/assets/javascript/firefly/reports.js diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 0b938206c0..00f8491e29 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -336,7 +336,8 @@ class AccountController extends BaseController /** * @param Account $account * - * @return $this|\Illuminate\Http\RedirectResponse + * @return $this + * @throws FireflyException */ public function update(Account $account) { @@ -345,13 +346,16 @@ class AccountController extends BaseController $acct = App::make('FireflyIII\Database\Account'); $data = Input::except('_token'); - switch($account->accountType->type) { + switch ($account->accountType->type) { default: throw new FireflyException('Cannot handle account type "' . e($account->accountType->type) . '"'); break; case 'Default account': $data['what'] = 'asset'; break; + case 'Beneficiary account': + $data['what'] = 'expense'; + break; } switch (Input::get('post_submit_action')) { diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index ede430a25b..57f1a49812 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -44,9 +44,9 @@ class GoogleChartController extends BaseController foreach ($accounts as $account) { //if ($current > Carbon::now()) { - // $row[] = 0; + // $row[] = 0; //} else { - $row[] = $account->balance($current); + $row[] = $account->balance($current); //} } @@ -60,6 +60,86 @@ class GoogleChartController extends BaseController } + /** + * @param $year + * + * @return \Illuminate\Http\JsonResponse + */ + public function yearInExp($year) + { + try { + $start = new Carbon('01-01-' . $year); + } catch (Exception $e) { + App::abort(500); + } + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Month', 'date'); + $chart->addColumn('Income', 'number'); + $chart->addColumn('Expenses', 'number'); + + /** @var \FireflyIII\Database\TransactionJournal $tj */ + $tj = App::make('FireflyIII\Database\TransactionJournal'); + + $end = clone $start; + $end->endOfYear(); + while ($start < $end) { + + // total income: + $income = $tj->getSumOfIncomesByMonth($start); + $expense = $tj->getSumOfExpensesByMonth($start); + + $chart->addRow(clone $start, $income, $expense); + $start->addMonth(); + } + + + $chart->generate(); + return Response::json($chart->getData()); + + } + /** + * @param $year + * + * @return \Illuminate\Http\JsonResponse + */ + public function yearInExpSum($year) + { + try { + $start = new Carbon('01-01-' . $year); + } catch (Exception $e) { + App::abort(500); + } + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Month', 'string'); + $chart->addColumn('Income', 'number'); + $chart->addColumn('Expenses', 'number'); + + /** @var \FireflyIII\Database\TransactionJournal $tj */ + $tj = App::make('FireflyIII\Database\TransactionJournal'); + + $end = clone $start; + $end->endOfYear(); + $income = 0; + $expense = 0; + while ($start < $end) { + + // total income: + $income += $tj->getSumOfIncomesByMonth($start); + $expense += $tj->getSumOfExpensesByMonth($start); + + $start->addMonth(); + } + $chart->addRow('Sum', $income, $expense); + + + + $chart->generate(); + return Response::json($chart->getData()); + + } + /** * @return \Illuminate\Http\JsonResponse */ diff --git a/app/controllers/ReportController.php b/app/controllers/ReportController.php index a313d89564..baab2c53a0 100644 --- a/app/controllers/ReportController.php +++ b/app/controllers/ReportController.php @@ -1,4 +1,5 @@ with('title','Reports')->with('mainTitleIcon','fa-line-chart'); + /** @var \FireflyIII\Database\TransactionJournal $journals */ + $journals = App::make('FireflyIII\Database\TransactionJournal'); + $journal = $journals->first(); + + $date = $journal->date; + $years = []; + while ($date <= Carbon::now()) { + $years[] = $date->format('Y'); + $date->addYear(); + } + + + return View::make('reports.index', compact('years'))->with('title', 'Reports')->with('mainTitleIcon', 'fa-line-chart'); + } + + /** + * @param $year + */ + public function year($year) + { + try { + $date = new Carbon('01-01-' . $year); + } catch (Exception $e) { + App::abort(500); + } + + /** @var \FireflyIII\Database\TransactionJournal $tj */ + $tj = App::make('FireflyIII\Database\TransactionJournal'); + + // get some sums going + $summary = []; + + + $end = clone $date; + $end->endOfYear(); + while ($date < $end) { + $summary[] = [ + 'month' => $date->format('F'), + 'income' => $tj->getSumOfIncomesByMonth($date), + 'expense' => $tj->getSumOfExpensesByMonth($date), + ]; + $date->addMonth(); + } + + + // draw some charts etc. + return View::make('reports.year', compact('summary'))->with('title', 'Reports')->with('mainTitleIcon', 'fa-line-chart')->with('subTitle', $year)->with( + 'subTitleIcon', 'fa-bar-chart' + )->with('year', $year); } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Ifaces/TransactionJournalInterface.php b/app/lib/FireflyIII/Database/Ifaces/TransactionJournalInterface.php index 8aebf540a0..e5085c7f19 100644 --- a/app/lib/FireflyIII/Database/Ifaces/TransactionJournalInterface.php +++ b/app/lib/FireflyIII/Database/Ifaces/TransactionJournalInterface.php @@ -19,4 +19,24 @@ interface TransactionJournalInterface */ public function getInDateRange(Carbon $start, Carbon $end); + /** + * Get the very first transaction journal. + * @return mixed + */ + public function first(); + + /** + * @param Carbon $date + * + * @return float + */ + public function getSumOfIncomesByMonth(Carbon $date); + + /** + * @param Carbon $date + * + * @return float + */ + public function getSumOfExpensesByMonth(Carbon $date); + } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal.php index e6d0d5a12f..5b37d5c2ae 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal.php @@ -12,6 +12,7 @@ use LaravelBook\Ardent\Ardent; use FireflyIII\Database\Ifaces\CommonDatabaseCalls; use FireflyIII\Database\Ifaces\CUD; use FireflyIII\Database\Ifaces\TransactionJournalInterface; + /** * Class TransactionJournal * @@ -29,6 +30,43 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData $this->setUser(\Auth::user()); } + /** + * @param Carbon $date + * + * @return float + */ + public function getSumOfIncomesByMonth(Carbon $date) + { + $end = clone $date; + $date->startOfMonth(); + $end->endOfMonth(); + $list = $this->getUser()->transactionjournals()->transactionTypes(['Deposit'])->before($end)->after($date)->get(['transaction_journals.*']); + $sum = 0; + /** @var \TransactionJournal $entry */ + foreach ($list as $entry) { + $sum += $entry->getAmount(); + } + return $sum; + } + + /** + * @param Carbon $date + * + * @return float + */ + public function getSumOfExpensesByMonth(Carbon $date) { + $end = clone $date; + $date->startOfMonth(); + $end->endOfMonth(); + $list = $this->getUser()->transactionjournals()->transactionTypes(['Withdrawal'])->before($end)->after($date)->get(['transaction_journals.*']); + $sum = 0; + /** @var \TransactionJournal $entry */ + foreach ($list as $entry) { + $sum += $entry->getAmount(); + } + return $sum; + } + /** * @param Carbon $start * @param Carbon $end @@ -40,6 +78,14 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData return $this->getuser()->transactionjournals()->withRelevantData()->before($end)->after($start)->get(); } + /** + * @return TransactionJournal + */ + public function first() + { + return $this->getUser()->transactionjournals()->orderBy('date', 'ASC')->first(); + } + /** * @param Ardent $model diff --git a/app/models/TransactionJournal.php b/app/models/TransactionJournal.php index 41c8f79805..08808110e4 100644 --- a/app/models/TransactionJournal.php +++ b/app/models/TransactionJournal.php @@ -4,242 +4,6 @@ use Carbon\Carbon; use LaravelBook\Ardent\Ardent; use LaravelBook\Ardent\Builder; -/** - * TransactionJournal - * - * @property integer $id - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property integer $user_id - * @property integer $transaction_type_id - * @property integer $transaction_currency_id - * @property string $description - * @property boolean $completed - * @property \Carbon\Carbon $date - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\Component[] $components - * @property-read \TransactionCurrency $transactionCurrency - * @property-read \TransactionType $transactionType - * @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions - * @property-read \User $user - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereId($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereCreatedAt($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereUpdatedAt($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereUserId($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereTransactionTypeId($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereTransactionCurrencyId($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDescription($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereCompleted($value) - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDate($value) - * @method static \TransactionJournal account($account) - * @method static \TransactionJournal after($date) - * @method static \TransactionJournal before($date) - * @method static \TransactionJournal defaultSorting() - * @method static \TransactionJournal moreThan($amount) - * @method static \TransactionJournal lessThan($amount) - * @method static \TransactionJournal onDate($date) - * @method static \TransactionJournal transactionTypes($types) - * @method static \TransactionJournal withRelevantData() - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property integer $recurring_transaction_id - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \RecurringTransaction $recurringTransaction - * @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereRecurringTransactionId($value) - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @method static \TransactionJournal accountIs($account) - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Budget[] $budgets - * @property-read \Illuminate\Database\Eloquent\Collection|\ - * 'Category[] $categories - */ class TransactionJournal extends Ardent { @@ -259,7 +23,7 @@ class TransactionJournal extends Ardent public function budgets() { return $this->belongsToMany( - 'Budget', 'component_transaction_journal', 'transaction_journal_id', 'component_id' + 'Budget', 'component_transaction_journal', 'transaction_journal_id', 'component_id' ); } @@ -269,7 +33,7 @@ class TransactionJournal extends Ardent public function categories() { return $this->belongsToMany( - 'Category', 'component_transaction_journal', 'transaction_journal_id', 'component_id' + 'Category', 'component_transaction_journal', 'transaction_journal_id', 'component_id' ); } @@ -281,6 +45,18 @@ class TransactionJournal extends Ardent return $this->belongsToMany('Component'); } + /** + * @return float + */ + public function getAmount() + { + foreach ($this->transactions as $t) { + if (floatval($t->amount) > 0) { + return floatval($t->amount); + } + } + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ @@ -312,7 +88,7 @@ class TransactionJournal extends Ardent /** * @param $query - * @param Carbon $date + * @param Carbon $date * * @return mixed */ @@ -323,7 +99,7 @@ class TransactionJournal extends Ardent /** * @param $query - * @param Carbon $date + * @param Carbon $date * * @return mixed */ @@ -340,8 +116,10 @@ class TransactionJournal extends Ardent public function scopeMoreThan(Builder $query, $amount) { if (is_null($this->joinedTransactions)) { - $query->leftJoin('transactions', 'transactions.transaction_journal_id', '=', - 'transaction_journals.id'); + $query->leftJoin( + 'transactions', 'transactions.transaction_journal_id', '=', + 'transaction_journals.id' + ); $this->joinedTransactions = true; } @@ -351,8 +129,10 @@ class TransactionJournal extends Ardent public function scopeLessThan(Builder $query, $amount) { if (is_null($this->joinedTransactions)) { - $query->leftJoin('transactions', 'transactions.transaction_journal_id', '=', - 'transaction_journals.id'); + $query->leftJoin( + 'transactions', 'transactions.transaction_journal_id', '=', + 'transaction_journals.id' + ); $this->joinedTransactions = true; } @@ -373,8 +153,10 @@ class TransactionJournal extends Ardent public function scopeTransactionTypes(Builder $query, array $types) { if (is_null($this->joinedTransactionTypes)) { - $query->leftJoin('transaction_types', 'transaction_types.id', '=', - 'transaction_journals.transaction_type_id'); + $query->leftJoin( + 'transaction_types', 'transaction_types.id', '=', + 'transaction_journals.transaction_type_id' + ); $this->joinedTransactionTypes = true; } $query->whereIn('transaction_types.type', $types); @@ -389,11 +171,11 @@ class TransactionJournal extends Ardent public function scopeWithRelevantData(Builder $query) { $query->with( - ['transactions' => function ($q) { - $q->orderBy('amount', 'ASC'); - }, 'transactiontype', 'components' => function ($q) { - $q->orderBy('class'); - }, 'transactions.account.accounttype','recurringTransaction'] + ['transactions' => function ($q) { + $q->orderBy('amount', 'ASC'); + }, 'transactiontype', 'components' => function ($q) { + $q->orderBy('class'); + }, 'transactions.account.accounttype', 'recurringTransaction'] ); } diff --git a/app/routes.php b/app/routes.php index 329edfa3d1..f9e515d144 100644 --- a/app/routes.php +++ b/app/routes.php @@ -163,6 +163,8 @@ Route::group( Route::get('/chart/account/{account}', ['uses' => 'GoogleChartController@accountBalanceChart']); Route::get('/chart/sankey/{account}/out', ['uses' => 'GoogleChartController@accountSankeyOutChart']); Route::get('/chart/sankey/{account}/in', ['uses' => 'GoogleChartController@accountSankeyInChart']); + Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']); + Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']); // google table controller Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); @@ -232,6 +234,7 @@ Route::group( // report controller: Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']); + Route::get('/reports/{year}', ['uses' => 'ReportController@year', 'as' => 'reports.year']); // search controller: Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']); diff --git a/app/views/reports/index.blade.php b/app/views/reports/index.blade.php index 888107e0e8..4511ce7b3c 100644 --- a/app/views/reports/index.blade.php +++ b/app/views/reports/index.blade.php @@ -1,10 +1,19 @@ @extends('layouts.default') @section('content')
    -
    -

    - Here be content. -

    +
    +
    +
    + Yearly reports +
    +
    +
      + @foreach($years as $year) +
    • {{$year}}
    • + @endforeach +
    +
    +
    @stop \ No newline at end of file diff --git a/app/views/reports/year.blade.php b/app/views/reports/year.blade.php new file mode 100644 index 0000000000..9ab2207b83 --- /dev/null +++ b/app/views/reports/year.blade.php @@ -0,0 +1,84 @@ +@extends('layouts.default') +@section('content') +
    +
    +
    +
    + Income vs. expenses +
    +
    +
    +
    +
    +
    +
    +
    +
    + Income vs. expenses +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + Summary +
    +
    + + + + @foreach($summary as $entry) + + @endforeach + + + + + + @foreach($summary as $entry) + + + @endforeach + + + + + @foreach($summary as $entry) + + + @endforeach + + + + @foreach($summary as $entry) + + @endforeach + + +
    {{$entry['month']}}Sum
    In{{mf($entry['income'])}}{{mf($inSum)}}
    Out{{mf($entry['expense']*-1)}}{{mf($outSum)}}
    Difference{{mf($entry['income']- $entry['expense'])}}{{mf($inSum + $outSum)}}
    +
    +
    +
    +
    + +@stop +@section('scripts') + + +{{HTML::script('assets/javascript/firefly/gcharts.options.js')}} +{{HTML::script('assets/javascript/firefly/gcharts.js')}} + + + +{{HTML::script('assets/javascript/firefly/reports.js')}} + +@stop \ No newline at end of file diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index edd171aec6..1b2831aafe 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -13,31 +13,31 @@ function drawChart() { $(function () { - //googleLineChart(); - /** - * get data from controller for home charts: - */ - $.getJSON('chart/home/account').success(function (data) { - //$('#accounts-chart').highcharts(options); - }); - - /** - * Get chart data for categories chart: - */ - $.getJSON('chart/home/categories').success(function (data) { - //$('#categories-chart'); - }); - - /** - * Get chart data for budget charts. - */ - $.getJSON('chart/home/budgets').success(function (data) { - //$('#budgets-chart'); - - }); - - $.getJSON('chart/home/recurring').success(function (data) { - //$('#recurring-chart'); - }); + ////googleLineChart(); + ///** + // * get data from controller for home charts: + // */ + //$.getJSON('chart/home/account').success(function (data) { + // //$('#accounts-chart').highcharts(options); + //}); + // + ///** + // * Get chart data for categories chart: + // */ + //$.getJSON('chart/home/categories').success(function (data) { + // //$('#categories-chart'); + //}); + // + ///** + // * Get chart data for budget charts. + // */ + //$.getJSON('chart/home/budgets').success(function (data) { + // //$('#budgets-chart'); + // + //}); + // + //$.getJSON('chart/home/recurring').success(function (data) { + // //$('#recurring-chart'); + //}); }); \ No newline at end of file diff --git a/public/assets/javascript/firefly/reports.js b/public/assets/javascript/firefly/reports.js new file mode 100644 index 0000000000..445304a04c --- /dev/null +++ b/public/assets/javascript/firefly/reports.js @@ -0,0 +1,7 @@ +google.setOnLoadCallback(drawChart); + + +function drawChart() { + googleColumnChart('chart/reports/income-expenses/' + year, 'income-expenses-chart'); + googleColumnChart('chart/reports/income-expenses-sum/' + year, 'income-expenses-sum-chart') +} \ No newline at end of file From 03729aa5ae38aea7757047db8b209c0fc5f741fc Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 2 Nov 2014 21:24:50 +0100 Subject: [PATCH 37/57] Chart cleanup --- app/controllers/GoogleChartController.php | 6 ++-- app/views/layouts/default.blade.php | 1 + public/assets/javascript/firefly/gcharts.js | 7 ++--- .../javascript/firefly/gcharts.options.js | 28 ++++++++++++++++--- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index 57f1a49812..4c72298b1c 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -231,8 +231,7 @@ class GoogleChartController extends BaseController /** @var \TransactionJournal $journal */ foreach ($journals as $journal) { if ($journal->transactionType->type == 'Withdrawal') { - $amount = floatval($journal->transactions[1]->amount); - $amount = $amount < 0 ? $amount * -1 : $amount; + $amount = $journal->getAmount(); $category = $journal->categories()->first(); if (!is_null($category)) { if (isset($data[$category->name])) { @@ -321,8 +320,7 @@ class GoogleChartController extends BaseController $unpaid['items'][] = $entry->name; $unpaid['amount'] += (($entry->amount_max + $entry->amount_min) / 2); } else { - $amount = floatval($journal->transactions[0]->amount); - $amount = $amount < 0 ? $amount * -1 : $amount; + $amount = $journal->getAmount(); $paid['items'][] = $journal->description; $paid['amount'] += $amount; } diff --git a/app/views/layouts/default.blade.php b/app/views/layouts/default.blade.php index 76b2c54814..b63fc91baf 100644 --- a/app/views/layouts/default.blade.php +++ b/app/views/layouts/default.blade.php @@ -17,6 +17,7 @@ {{HTML::style('assets/stylesheets/metisMenu/metisMenu.min.css')}} {{HTML::style('assets/stylesheets/sbadmin/sb.css')}} {{HTML::style('assets/stylesheets/fa/css/font-awesome.min.css')}} + @yield('styles') + @include('partials.date_nav') +
    +
    + +
    +@foreach($budgets as $budget) +
    +
    +
    + @if($budget->currentRep) + {{{$budget->name}}} + @else + {{{$budget->name}}} + @endif +
    +
    + +

    + @if($budget->currentRep) + + @else + + @endif +

    + +

    + + + @if($budget->currentRep) + + Budgeted: + + + @if($budget->limit > $budget->spent) + + @else + + @endif + + @else + No budget + + + + @endif + +

    +

    + Spent: {{mf($budget->spent)}} +

    +
    +
    +
    + +@endforeach +
    + + + +@foreach($budgets as $budget) +{{-- +
    +
    +
    +
    + {{$budget->name}} +
    +
    + +
    +
    + +
    +
    +
    +
    + @if($budget->pct > 0) + +
    + +
    + @else + + + @endif + +
    +
    +
    +
    +--}} +@endforeach + + + + + +@stop +@section('scripts') +{{HTML::script('assets/javascript/firefly/budgets.js')}} +@stop \ No newline at end of file diff --git a/app/views/index.blade.php b/app/views/index.blade.php index 9811649d62..66857d7cfd 100644 --- a/app/views/index.blade.php +++ b/app/views/index.blade.php @@ -44,7 +44,7 @@
    diff --git a/app/views/limits/delete.blade.php b/app/views/limits/delete.blade.php index c3cffd29d7..4c3bd9b8b7 100644 --- a/app/views/limits/delete.blade.php +++ b/app/views/limits/delete.blade.php @@ -31,9 +31,9 @@
    @if(Input::get('from') == 'date') - Cancel + Cancel @else - Cancel + Cancel @endif
    diff --git a/app/views/partials/menu.blade.php b/app/views/partials/menu.blade.php index c294dbefb5..21a5068f89 100644 --- a/app/views/partials/menu.blade.php +++ b/app/views/partials/menu.blade.php @@ -48,7 +48,7 @@ -
  • - Budgets - - - - +
  • + Budgets
  • -->
  • - Budget + Budget
  • Category diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 3ba8bf53a4..4adaf1f253 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -104,21 +104,6 @@ @stop diff --git a/public/assets/javascript/firefly/budgets.js b/public/assets/javascript/firefly/budgets.js new file mode 100644 index 0000000000..68d880c4ee --- /dev/null +++ b/public/assets/javascript/firefly/budgets.js @@ -0,0 +1,164 @@ +$(function () { + updateRanges(); + $('input[type="range"]').change(updateSingleRange); + $('input[type="range"]').on('input', updateSingleRange); + $('input[type="number"]').on('change',updateSingleRange); + $('input[type="number"]').on('input',updateSingleRange); + $('.updateIncome').on('click', updateIncome); + +}); + + +function updateSingleRange(e) { + // get some values: + var input = $(e.target); + var id = input.data('id'); + var value = parseInt(input.val()); + + var spent = parseFloat($('#spent-' + id).data('value')); + console.log('Spent vs budgeted: ' + spent + ' vs ' + value) + + // update small display: + if(value > 0) { + // show the input: + $('#budget-info-' + id +' span').show(); + $('#budget-info-' + id +' input').show(); + + // update the text: + $('#budget-description-' + id).text('Budgeted: '); + + //if(value < spent) { + // $('#budgeted-' + id).html('Budgeted: \u20AC ' + value.toFixed(2) + ''); + //} else { + // $('#budgeted-' + id).html('Budgeted: \u20AC ' + value.toFixed(2) + ''); + //} + } else { + console.log('Set to zero!'); + // hide the input: + $('#budget-info-' + id +' span').hide(); + $('#budget-info-' + id +' input').hide(); + + // update the text: + $('#budget-description-' + id).html('No budget'); + + //$('#budgeted-' + id).html('No budget'); + } + // update the range display text: + $('#budget-range-display-' + id).text('\u20AC ' + value.toFixed(2)); + + // send a post to Firefly to update the amount: + console.log('Value is: ' + value); + $.post('budgets/amount/' + id, {amount: value}).success(function (data) { + console.log('Budget ' + data.name + ' updated!'); + // update the link if relevant: + $('#budget-link-' + id).attr('href','budgets/show/' + id + '/' + data.repetition); + }); + if(input.attr('type') == 'number') { + // update the range-input: + $('#budget-range-' + id).val(value); + } else { + // update the number-input: + $('#budget-info-' + id +' input').val(value); + } + + // update or hide the bar, whichever is necessary. + + updateTotal(); + return value; +} + +function updateTotal() { + var sum = 0; + $('input[type="range"]').each(function (i, v) { + // get some values: + sum += parseInt($(v).val()); + }); + + /** + * Update total sum: + */ + var totalAmount = parseInt($('#totalAmount').data('value')); + if (sum <= totalAmount) { + var pct = sum / totalAmount * 100; + $('#progress-bar-default').css('width', pct + '%'); + $('#progress-bar-warning').css('width', '0'); + $('#progress-bar-danger').css('width', '0'); + $('#budgetedAmount').text('\u20AC ' + sum.toFixed(2)).addClass('text-success').removeClass('text-danger'); + } else { + // we gaan er X overheen, + + var pct = totalAmount / sum * 100; + console.log(pct) + var danger = 100 - pct; + var err = 100 - danger; + $('#progress-bar-default').css('width', 0); + $('#progress-bar-warning').css('width', err + '%'); + $('#progress-bar-danger').css('width', danger + '%'); + $('#budgetedAmount').text('\u20AC ' + sum.toFixed(2)).addClass('text-danger').removeClass('text-success'); + } + +} + +function updateIncome(e) { + $('#monthlyBudgetModal').empty().load('budgets/income').modal('show'); + + return false; +} + +function updateRanges() { + /** + * Update all ranges. + */ + var sum = 0; + $('input[type="range"]').each(function (i, v) { + // get some values: + var input = $(v); + var id = input.data('id'); + var value = parseInt(input.val()); + + // calculate sum: + sum += value + + // update small display: + $('#budget-range-display-' + id).text('\u20AC ' + value.toFixed(2)); + + // update progress bar (if relevant) + var barHolder = $('#budget-progress-' + id); + var spent = parseFloat(barHolder.data('spent')); + if (value > 0 && spent > 0) { + console.log('Add bar') + //barHolder.append($('
    ')); + } + + // send a post to Firefly to update the amount: + $.post('budgets/amount/' + id, {amount: value}).success(function (data) { + console.log('Budget ' + data.name + ' updated!'); + }); + + }); + + /** + * Update total sum: + */ + var totalAmount = parseInt($('#totalAmount').data('value')); + if (sum <= totalAmount) { + var pct = sum / totalAmount * 100; + $('#progress-bar-default').css('width', pct + '%'); + $('#progress-bar-warning').css('width', '0'); + $('#progress-bar-danger').css('width', '0'); + $('#budgetedAmount').text('\u20AC ' + sum.toFixed(2)).addClass('text-success').removeClass('text-danger'); + } else { + // we gaan er X overheen, + + var pct = totalAmount / sum * 100; + console.log(pct) + var danger = 100 - pct; + var err = 100 - danger; + $('#progress-bar-default').css('width', 0); + $('#progress-bar-warning').css('width', err + '%'); + $('#progress-bar-danger').css('width', danger + '%'); + $('#budgetedAmount').text('\u20AC ' + sum.toFixed(2)).addClass('text-danger').removeClass('text-success'); + } + + +} \ No newline at end of file diff --git a/public/assets/javascript/firefly/piggybanks.js b/public/assets/javascript/firefly/piggybanks.js index 8c228d4de1..3d4d4739c0 100644 --- a/public/assets/javascript/firefly/piggybanks.js +++ b/public/assets/javascript/firefly/piggybanks.js @@ -11,7 +11,6 @@ function addMoney(e) { } function removeMoney(e) { - var pigID = parseInt($(e.target).data('id')); var pigID = parseInt($(e.target).data('id')); $('#moneyManagementModal').empty().load('piggybanks/remove/' + pigID).modal('show'); From 21f362c7b98316bf1e50f52a77e0d624b73366ad Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 5 Nov 2014 19:57:56 +0100 Subject: [PATCH 40/57] Lots of stuff for budgets, accounts and others! --- app/controllers/AccountController.php | 7 +- app/controllers/BudgetController.php | 104 ++++++++++++++---- .../Trigger/Limits/EloquentLimitTrigger.php | 3 - app/lib/FireflyIII/Database/Budget.php | 3 +- app/models/Limit.php | 6 + app/routes.php | 9 +- app/views/accounts/edit.blade.php | 2 +- app/views/budgets/edit.blade.php | 42 +++---- app/views/budgets/index.blade.php | 31 +++++- app/views/piggybanks/edit.blade.php | 2 +- bootstrap/start.php | 8 +- 11 files changed, 151 insertions(+), 66 deletions(-) diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 00f8491e29..8e10e9f830 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -356,6 +356,9 @@ class AccountController extends BaseController case 'Beneficiary account': $data['what'] = 'expense'; break; + case 'Revenue account': + $data['what'] = 'revenue'; + break; } switch (Input::get('post_submit_action')) { @@ -363,14 +366,14 @@ class AccountController extends BaseController throw new FireflyException('Cannot handle post_submit_action "' . e(Input::get('post_submit_action')) . '"'); break; case 'create_another': - case 'store': + case 'update': $messages = $acct->validate($data); /** @var MessageBag $messages ['errors'] */ if ($messages['errors']->count() > 0) { Session::flash('warnings', $messages['warnings']); Session::flash('successes', $messages['successes']); Session::flash('error', 'Could not save account: ' . $messages['errors']->first()); - return Redirect::route('accounts.create', $data['what'])->withInput()->withErrors($messages['errors']); + return Redirect::route('accounts.edit', $account->id)->withInput()->withErrors($messages['errors']); } // store! $acct->update($account, $data); diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index 21aed96411..a564f1b7fa 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -5,6 +5,7 @@ use Firefly\Exception\FireflyException; use Firefly\Helper\Controllers\BudgetInterface as BI; use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI; use FireflyIII\Exception\NotImplementedException; +use Illuminate\Support\MessageBag; class BudgetController extends BaseController @@ -36,7 +37,7 @@ class BudgetController extends BaseController public function amount(Budget $budget) { $amount = intval(Input::get('amount')); - $date = Session::get('start'); + $date = Session::get('start'); /** @var \Limit $limit */ $limit = $budget->limits()->where('startdate', $date->format('Y-m-d'))->first(); if (!$limit) { @@ -57,10 +58,10 @@ class BudgetController extends BaseController } // try to find the limit repetition for this limit: $repetition = $limit->limitrepetitions()->first(); - if($repetition) { - return Response::json(['name' => $budget->name,'repetition' => $repetition->id]); + if ($repetition) { + return Response::json(['name' => $budget->name, 'repetition' => $repetition->id]); } else { - return Response::json(['name' => $budget->name,'repetition' => null]); + return Response::json(['name' => $budget->name, 'repetition' => null]); } } @@ -82,7 +83,7 @@ class BudgetController extends BaseController foreach ($budgets as $budget) { $budget->spent = $repos->spentInMonth($budget, $date); - $budget->pct = 0; + $budget->pct = 0; $budget->limit = 0; /** @var \Limit $limit */ @@ -91,8 +92,8 @@ class BudgetController extends BaseController foreach ($limit->limitrepetitions as $repetition) { if ($repetition->startdate == $date) { $budget->currentRep = $repetition; - $budget->limit = floatval($repetition->amount); - if($budget->limit > $budget->spent) { + $budget->limit = floatval($repetition->amount); + if ($budget->limit > $budget->spent) { // not overspent: $budget->pct = 30; } else { @@ -109,6 +110,9 @@ class BudgetController extends BaseController return View::make('budgets.index', compact('budgets'))->with('budgetAmount', $budgetAmount); } + /** + * @return $this + */ public function updateIncome() { $date = Session::get('start'); @@ -118,6 +122,21 @@ class BudgetController extends BaseController return View::make('budgets.income')->with('amount', $budgetAmount)->with('date', $date); } + /** + * @param Budget $budget + * @param LimitRepetition $repetition + * + * @return \Illuminate\View\View + */ + public function show(Budget $budget, LimitRepetition $repetition = null) + { + if (!is_null($repetition) && $repetition->limit->budget->id != $budget->id) { + App::abort(500); + } + + return View::make('budgets.show'); + } + @@ -152,13 +171,17 @@ class BudgetController extends BaseController //// return Redirect::route('budgets.index.budget'); // // } -// public function edit(Budget $budget) -// { -// throw new NotImplementedException; -//// return View::make('budgets.edit')->with('budget', $budget) -//// ->with('subTitle', 'Edit budget "' . $budget->name . '"'); -// -// } + /** + * @param Budget $budget + * + * @return $this + */ + public function edit(Budget $budget) + { + Session::flash('prefilled', ['name' => $budget->name]); + return View::make('budgets.edit')->with('budget', $budget)->with('subTitle', 'Edit budget "' . $budget->name . '"'); + + } // /** // * @return $this|\Illuminate\View\View @@ -267,13 +290,50 @@ class BudgetController extends BaseController // // } // -// /** -// * @param Budget $budget -// * -// * @return $this|\Illuminate\Http\RedirectResponse -// */ -// public function update(Budget $budget) -// { + /** + * @param Budget $budget + * + * @throws FireflyException + */ + public function update(Budget $budget) + { + + /** @var \FireflyIII\Database\Budget $repos */ + $repos = App::make('FireflyIII\Database\Budget'); + $data = Input::except('_token'); + + switch (Input::get('post_submit_action')) { + default: + throw new FireflyException('Cannot handle post_submit_action "' . e(Input::get('post_submit_action')) . '"'); + break; + case 'create_another': + case 'update': + $messages = $repos->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save account: ' . $messages['errors']->first()); + return Redirect::route('budgets.edit', $budget->id)->withInput()->withErrors($messages['errors']); + } + // store! + $repos->update($budget, $data); + Session::flash('success', 'Account updated!'); + + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('budgets.edit', $budget->id); + } else { + return Redirect::route('budgets.edit', $budget->id); + } + case 'validate_only': + $messageBags = $repos->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); + return Redirect::route('budgets.edit', $budget->id)->withInput(); + break; + } + // $budget = $this->_repository->update($budget, Input::all()); // if ($budget->validate()) { // Event::fire('budgets.update', [$budget]); @@ -290,7 +350,7 @@ class BudgetController extends BaseController // return Redirect::route('budgets.edit', $budget->id)->withInput()->withErrors($budget->errors()); // } // -// } + } // public function nobudget($view = 'session') { // switch($view) { diff --git a/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php b/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php index bbdf2360c9..67f1e4dd03 100644 --- a/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php +++ b/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php @@ -126,9 +126,6 @@ class EloquentLimitTrigger // remove and recreate limit repetitions. // if limit is not repeating, simply update the repetition to match the limit, // even though deleting everything is easier. - foreach ($limit->limitrepetitions()->get() as $l) { - $l->delete(); - } $limit->createRepetition($limit->startdate); return true; diff --git a/app/lib/FireflyIII/Database/Budget.php b/app/lib/FireflyIII/Database/Budget.php index 867237b802..d707f33adf 100644 --- a/app/lib/FireflyIII/Database/Budget.php +++ b/app/lib/FireflyIII/Database/Budget.php @@ -2,6 +2,7 @@ namespace FireflyIII\Database; use Carbon\Carbon; +use FireflyIII\Exception\NotImplementedException; use Illuminate\Support\MessageBag; use LaravelBook\Ardent\Ardent; use Illuminate\Support\Collection; @@ -74,7 +75,7 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface */ public function validate(array $model) { - // TODO: Implement validate() method. + throw new NotImplementedException; } /** diff --git a/app/models/Limit.php b/app/models/Limit.php index 0255c0429e..49571e2d25 100644 --- a/app/models/Limit.php +++ b/app/models/Limit.php @@ -115,6 +115,12 @@ class Limit extends Ardent if (isset($repetition->id)) { \Event::fire('limits.repetition', [$repetition]); } + } else if($count == 1) { + // update this one: + $repetition = $this->limitrepetitions()->where('startdate', $start->format('Y-m-d'))->where('enddate', $end->format('Y-m-d'))->first(); + $repetition->amount = $this->amount; + $repetition->save(); + } } diff --git a/app/routes.php b/app/routes.php index dc13af8700..83f840415a 100644 --- a/app/routes.php +++ b/app/routes.php @@ -144,11 +144,11 @@ Route::group( #Route::get('/budgets/date', ['uses' => 'BudgetController@indexByDate', 'as' => 'budgets.index.date']); #Route::get('/budgets/budget', ['uses' => 'BudgetController@indexByBudget', 'as' => 'budgets.index.budget']); - #Route::get('/budgets/create', ['uses' => 'BudgetController@create', 'as' => 'budgets.create']); + Route::get('/budgets/create', ['uses' => 'BudgetController@create', 'as' => 'budgets.create']); #Route::get('/budgets/nobudget/{period}', ['uses' => 'BudgetController@nobudget', 'as' => 'budgets.nobudget']); - #Route::get('/budgets/edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'budgets.edit']); - #Route::get('/budgets/delete/{budget}', ['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']); + Route::get('/budgets/edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'budgets.edit']); + Route::get('/budgets/delete/{budget}', ['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']); // category controller: Route::get('/categories', ['uses' => 'CategoryController@index', 'as' => 'categories.index']); @@ -276,8 +276,9 @@ Route::group( // budget controller: Route::post('/budgets/income', ['uses' => 'BudgetController@postUpdateIncome', 'as' => 'budgets.postIncome']); + Route::post('/budgets/update/{budget}', ['uses' => 'BudgetController@update', 'as' => 'budgets.update']); #Route::post('/budgets/store', ['uses' => 'BudgetController@store', 'as' => 'budgets.store']); - #Route::post('/budgets/update/{budget}', ['uses' => 'BudgetController@update', 'as' => 'budgets.update']); + #Route::post('/budgets/destroy/{budget}', ['uses' => 'BudgetController@destroy', 'as' => 'budgets.destroy']); // category controller diff --git a/app/views/accounts/edit.blade.php b/app/views/accounts/edit.blade.php index eccbd863a0..39e14486ca 100644 --- a/app/views/accounts/edit.blade.php +++ b/app/views/accounts/edit.blade.php @@ -45,7 +45,7 @@ Options
  • - {{Form::ffOptionsList('create','account')}} + {{Form::ffOptionsList('update','account')}}
    diff --git a/app/views/budgets/edit.blade.php b/app/views/budgets/edit.blade.php index f179965866..baeeb1cc8c 100644 --- a/app/views/budgets/edit.blade.php +++ b/app/views/budgets/edit.blade.php @@ -7,40 +7,34 @@ {{Form::open(['class' => 'form-horizontal','url' => route('budgets.update',$budget->id)])}} - -{{Form::hidden('from',e(Input::get('from')))}} -
    -

    Mandatory fields

    - -
    - -
    - - @if($errors->has('name')) -

    {{$errors->first('name')}}

    - @else - For example: groceries, bills - @endif +
    +
    + Mandatory fields +
    +
    + {{Form::ffText('name')}}
    - +

    + +

    - -
    - -
    - -
    -
    - + +
    +
    + Options +
    +
    + {{Form::ffOptionsList('update','budget')}}
    - {{Form::close()}} diff --git a/app/views/budgets/index.blade.php b/app/views/budgets/index.blade.php index bdc4c703e1..82a035cfcf 100644 --- a/app/views/budgets/index.blade.php +++ b/app/views/budgets/index.blade.php @@ -29,7 +29,6 @@
    -

    @@ -39,7 +38,7 @@
    @foreach($budgets as $budget) -
    +
    @if($budget->currentRep) @@ -47,6 +46,22 @@ @else {{{$budget->name}}} @endif + + + +
    +
    + + +
    +
    +
    @@ -75,7 +90,7 @@ @else No budget - + @endif @@ -86,8 +101,16 @@
    - @endforeach +
    +
    +
    + Create budget +
    + +
    diff --git a/app/views/piggybanks/edit.blade.php b/app/views/piggybanks/edit.blade.php index 69169a5279..3cdead892d 100644 --- a/app/views/piggybanks/edit.blade.php +++ b/app/views/piggybanks/edit.blade.php @@ -17,7 +17,7 @@

    diff --git a/bootstrap/start.php b/bootstrap/start.php index 7edb58f2c9..1e76ce97dd 100644 --- a/bootstrap/start.php +++ b/bootstrap/start.php @@ -9,16 +9,16 @@ if (!function_exists('mf')) { $string = number_format($n, 2, ',', '.'); if ($coloured === true && $n === 0.0) { - return '€ ' . $string . ''; + return '€ ' . $string . ''; } if ($coloured === true && $n > 0) { - return '€ ' . $string . ''; + return '€ ' . $string . ''; } if ($coloured === true && $n < 0) { - return '€ ' . $string . ''; + return '€ ' . $string . ''; } - return '€ ' . $string; + return '€ ' . $string; } } From 01b0a1058d219257a14d655d8e9d6462bcf9fee0 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 5 Nov 2014 21:22:31 +0100 Subject: [PATCH 41/57] Form fix. --- app/controllers/PiggybankController.php | 2 +- app/routes.php | 2 +- app/views/piggybanks/edit.blade.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index fd1c871238..d82411bc0f 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -304,7 +304,7 @@ class PiggybankController extends BaseController $accounts[$account->id] = [ 'name' => $account->name, 'balance' => $account->balance(), - 'leftForPiggybanks' => $account->balance() - $piggybank->savedSoFar, + 'leftForPiggybanks' => $repos->leftOnAccount($account), 'sumOfSaved' => $piggybank->savedSoFar, 'sumOfTargets' => floatval($piggybank->targetamount), 'leftToSave' => $piggybank->leftToSave diff --git a/app/routes.php b/app/routes.php index 83f840415a..0d4c5bd6fa 100644 --- a/app/routes.php +++ b/app/routes.php @@ -297,7 +297,7 @@ Route::group( // piggy bank controller Route::post('/piggybanks/store', ['uses' => 'PiggybankController@store', 'as' => 'piggybanks.store']); #Route::post('/piggybanks/store/repeated', ['uses' => 'PiggybankController@storeRepeated', 'as' => 'piggybanks.store.repeated']); - #Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); + Route::post('/piggybanks/update/{piggybank}', ['uses' => 'PiggybankController@update', 'as' => 'piggybanks.update']); Route::post('/piggybanks/destroy/{piggybank}', ['uses' => 'PiggybankController@destroy', 'as' => 'piggybanks.destroy']); #Route::post('/piggybanks/mod/{piggybank}', ['uses' => 'PiggybankController@modMoney', 'as' => 'piggybanks.modMoney']); Route::post('/piggybanks/add/{piggybank}', ['uses' => 'PiggybankController@postAdd', 'as' => 'piggybanks.add']); diff --git a/app/views/piggybanks/edit.blade.php b/app/views/piggybanks/edit.blade.php index 3cdead892d..f46f1fadce 100644 --- a/app/views/piggybanks/edit.blade.php +++ b/app/views/piggybanks/edit.blade.php @@ -1,6 +1,6 @@ @extends('layouts.default') @section('content') -{{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.store')])}} +{{Form::open(['class' => 'form-horizontal','url' => route('piggybanks.update',$piggybank->id)])}}
    From f7117d47c22cc5d3f285890aa1f40c22e85882b3 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 5 Nov 2014 21:23:23 +0100 Subject: [PATCH 42/57] Whoops, bugfix. --- app/controllers/PiggybankController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index d82411bc0f..aa008c0088 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -310,7 +310,6 @@ class PiggybankController extends BaseController 'leftToSave' => $piggybank->leftToSave ]; } else { - $accounts[$account->id]['leftForPiggybanks'] -= $piggybank->savedSoFar; $accounts[$account->id]['sumOfSaved'] += $piggybank->savedSoFar; $accounts[$account->id]['sumOfTargets'] += floatval($piggybank->targetamount); $accounts[$account->id]['leftToSave'] += $piggybank->leftToSave; From f814f45e36ab04d11375de1064b99ad3001c2f36 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Wed, 5 Nov 2014 21:37:24 +0100 Subject: [PATCH 43/57] Test fix for budgeting. --- app/controllers/BudgetController.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index a564f1b7fa..5c587842e3 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -52,9 +52,13 @@ class BudgetController extends BaseController Event::fire('limits.store', [$limit]); } else { - $limit->amount = $amount; - $limit->save(); - Event::fire('limits.update', [$limit]); + if ($amount > 0) { + $limit->amount = $amount; + $limit->save(); + Event::fire('limits.update', [$limit]); + } else { + $limit->delete(); + } } // try to find the limit repetition for this limit: $repetition = $limit->limitrepetitions()->first(); From ddea7d696af8329b857f5a5b612d828f889094df Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 6 Nov 2014 07:38:15 +0100 Subject: [PATCH 44/57] Delete and update routines. --- app/controllers/BudgetController.php | 131 ++++++++++++++----------- app/lib/FireflyIII/Database/Budget.php | 55 ++++++++++- app/routes.php | 5 +- app/views/budgets/create.blade.php | 121 ++++------------------- app/views/budgets/delete.blade.php | 47 ++++----- 5 files changed, 164 insertions(+), 195 deletions(-) diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index 5c587842e3..74ed304099 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -18,6 +18,9 @@ class BudgetController extends BaseController View::share('mainTitleIcon', 'fa-tasks'); } + /** + * @return \Illuminate\Http\RedirectResponse + */ public function postUpdateIncome() { /** @var \Firefly\Helper\Preferences\PreferencesHelperInterface $preferences */ @@ -141,40 +144,35 @@ class BudgetController extends BaseController return View::make('budgets.show'); } + /** + * @return $this + */ + public function create() + { + return View::make('budgets.create')->with('subTitle', 'Create a new budget'); + } + /** + * @param Budget $budget + * + * @return $this + */ + public function delete(Budget $budget) + { + return View::make('budgets.delete')->with('budget', $budget)->with('subTitle', 'Delete budget "' . $budget->name . '"'); + } + public function destroy(Budget $budget) + { + /** @var \FireflyIII\Database\Budget $repos */ + $repos = App::make('FireflyIII\Database\Budget'); + // remove budget + $repos->destroy($budget); + Session::flash('success', 'The budget was deleted.'); + return Redirect::route('budgets.index'); + + } -// -// public function create() -// { -// throw new NotImplementedException; -//// $periods = \Config::get('firefly.periods_to_text'); -//// -//// return View::make('budgets.create')->with('periods', $periods)->with('subTitle', 'Create a new budget'); -// } -// -// public function delete(Budget $budget) -// { -// throw new NotImplementedException; -//// return View::make('budgets.delete')->with('budget', $budget) -//// ->with('subTitle', 'Delete budget "' . $budget->name . '"'); -// } -// -// public function destroy(Budget $budget) -// { -// throw new NotImplementedException; -//// // remove budget -//// Event::fire('budgets.destroy', [$budget]); // just before deletion. -//// $this->_repository->destroy($budget); -//// Session::flash('success', 'The budget was deleted.'); -//// -//// // redirect: -//// if (Input::get('from') == 'date') { -//// return Redirect::route('budgets.index'); -//// } -//// return Redirect::route('budgets.index.budget'); -// -// } /** * @param Budget $budget * @@ -266,33 +264,48 @@ class BudgetController extends BaseController // ->with('subTitle', 'Overview for ' . $title); // } // -// /** -// * @return \Illuminate\Http\RedirectResponse -// */ -// public function store() -// { -// -// $budget = $this->_repository->store(Input::all()); -// if ($budget->validate()) { -// Event::fire('budgets.store', [$budget]); -// Session::flash('success', 'Budget created!'); -// -// if (Input::get('create') == '1') { -// return Redirect::route('budgets.create', ['from' => Input::get('from')]); -// } -// -// if (Input::get('from') == 'date') { -// return Redirect::route('budgets.index'); -// } else { -// return Redirect::route('budgets.index.budget'); -// } -// } else { -// Session::flash('error', 'Could not save the new budget'); -// -// return Redirect::route('budgets.create')->withInput()->withErrors($budget->errors()); -// } -// -// } + /** + * @return \Illuminate\Http\RedirectResponse + */ + public function store() + { + /** @var \FireflyIII\Database\Budget $repos */ + $repos = App::make('FireflyIII\Database\Budget'); + $data = Input::except('_token'); + + switch ($data['post_submit_action']) { + default: + throw new FireflyException('Cannot handle post_submit_action "' . e($data['post_submit_action']) . '"'); + break; + case 'create_another': + case 'store': + $messages = $repos->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save budget: ' . $messages['errors']->first()); + return Redirect::route('budgets.create')->withInput()->withErrors($messages['errors']); + } + // store! + $repos->store($data); + Session::flash('success', 'New budget stored!'); + + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('budgets.create'); + } else { + return Redirect::route('budgets.index'); + } + break; + case 'validate_only': + $messageBags = $repos->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); + return Redirect::route('budgets.create')->withInput(); + break; + } + } // /** * @param Budget $budget @@ -317,7 +330,7 @@ class BudgetController extends BaseController if ($messages['errors']->count() > 0) { Session::flash('warnings', $messages['warnings']); Session::flash('successes', $messages['successes']); - Session::flash('error', 'Could not save account: ' . $messages['errors']->first()); + Session::flash('error', 'Could not save budget: ' . $messages['errors']->first()); return Redirect::route('budgets.edit', $budget->id)->withInput()->withErrors($messages['errors']); } // store! diff --git a/app/lib/FireflyIII/Database/Budget.php b/app/lib/FireflyIII/Database/Budget.php index d707f33adf..7d82420abb 100644 --- a/app/lib/FireflyIII/Database/Budget.php +++ b/app/lib/FireflyIII/Database/Budget.php @@ -49,7 +49,8 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface */ public function destroy(Ardent $model) { - // TODO: Implement destroy() method. + $model->delete(); + return true; } /** @@ -75,7 +76,34 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface */ public function validate(array $model) { - throw new NotImplementedException; + $warnings = new MessageBag; + $successes = new MessageBag; + $errors = new MessageBag; + + if(isset($model['name'])) { + if(strlen($model['name']) < 1) { + $errors->add('name', 'Name is too short'); + } + if(strlen($model['name']) > 200) { + $errors->add('name', 'Name is too long'); + + } + } else { + $errors->add('name', 'Name is mandatory'); + } + $validator = \Validator::make([$model], \Budget::$rules); + if ($validator->invalid()) { + $errors->merge($errors); + } + if(!$errors->has('name')) { + $successes->add('name','OK'); + } + + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; } /** @@ -85,7 +113,17 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface */ public function store(array $data) { - // TODO: Implement store() method. + $data['user_id'] = $this->getUser()->id; + + $budget = new \Budget($data); + $budget->class = 'Budget'; + + if (!$budget->validate()) { + var_dump($budget->errors()->all()); + exit; + } + $budget->save(); + return $budget; } /** @@ -179,6 +217,15 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface */ public function update(Ardent $model, array $data) { - // TODO: Implement update() method. + $model->name = $data['name']; + if (!$model->validate()) { + var_dump($model->errors()->all()); + exit; + } + + + $model->save(); + + return true; } } \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 0d4c5bd6fa..0444f2c99a 100644 --- a/app/routes.php +++ b/app/routes.php @@ -277,9 +277,8 @@ Route::group( // budget controller: Route::post('/budgets/income', ['uses' => 'BudgetController@postUpdateIncome', 'as' => 'budgets.postIncome']); Route::post('/budgets/update/{budget}', ['uses' => 'BudgetController@update', 'as' => 'budgets.update']); - #Route::post('/budgets/store', ['uses' => 'BudgetController@store', 'as' => 'budgets.store']); - - #Route::post('/budgets/destroy/{budget}', ['uses' => 'BudgetController@destroy', 'as' => 'budgets.destroy']); + Route::post('/budgets/store', ['uses' => 'BudgetController@store', 'as' => 'budgets.store']); + Route::post('/budgets/destroy/{budget}', ['uses' => 'BudgetController@destroy', 'as' => 'budgets.destroy']); // category controller Route::post('/categories/store', ['uses' => 'CategoryController@store', 'as' => 'categories.store']); diff --git a/app/views/budgets/create.blade.php b/app/views/budgets/create.blade.php index b15dc00408..0b20da4663 100644 --- a/app/views/budgets/create.blade.php +++ b/app/views/budgets/create.blade.php @@ -1,118 +1,39 @@ @extends('layouts.default') @section('content') -
    -
    -

    Use budgets to organize and limit your expenses.

    -

    - Firefly uses the envelope system. Every budget - is an envelope in which you put money every [period]. Expenses allocated to each budget are paid from this - (virtual) envelope. -

    -

    - When the envelope is empty, you must stop spending on the budget. If the envelope still has some money left at the - end of the [period], congratulations! You have saved money! -

    -
    -
    - {{Form::open(['class' => 'form-horizontal','url' => route('budgets.store')])}} - -{{Form::hidden('from',e(Input::get('from')))}} -
    -
    -

    Mandatory fields

    - -
    - -
    - - @if($errors->has('name')) -

    {{$errors->first('name')}}

    - @else - For example: groceries, bills - @endif +
    +
    +
    + Mandatory fields +
    +
    + {{Form::ffText('name')}}
    +

    + +

    -
    -

    Optional fields

    - -
    - -
    -
    - - +
    + +
    +
    + Options
    - - @if($errors->has('amount')) -

    {{$errors->first('amount')}}

    - @else - What's the most you're willing to spend in this budget? This amount is "put" in the virtual - envelope. - @endif -
    -
    - -
    - -
    - {{Form::select('repeat_freq',$periods,Input::old('repeat_freq') ?: 'monthly',['class' => 'form-control'])}} - @if($errors->has('repeat_freq')) -

    {{$errors->first('repeat_freq')}}

    - @else - How long will the envelope last? A week, a month, or even longer? - @endif -
    -
    - -
    - -
    -
    - +
    + {{Form::ffOptionsList('create','budget')}}
    - @if($errors->has('repeats')) -

    {{$errors->first('repeats')}}

    - @else - If you want, Firefly can automatically recreate the "envelope" and fill it again - when the timespan above has expired. Be careful with this option though. It makes it easier - to fall back to old habits. - Instead, you should recreate the envelope yourself each [period]. - @endif
    +
    -
    +
    -
    -
    - -
    - -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    {{Form::close()}} diff --git a/app/views/budgets/delete.blade.php b/app/views/budgets/delete.blade.php index 4f352f1238..96775a8357 100644 --- a/app/views/budgets/delete.blade.php +++ b/app/views/budgets/delete.blade.php @@ -1,42 +1,31 @@ @extends('layouts.default') @section('content') +{{Form::open(['class' => 'form-horizontal','url' => route('budgets.destroy',$budget->id)])}}
    -
    -

    - Remember that deleting something is permanent. -

    +
    +
    +
    + Delete budget "{{{$budget->name}}}" +
    +
    +

    + Are you sure? +

    + +

    + + Cancel +

    +
    +
    -{{Form::open(['class' => 'form-horizontal','url' => route('budgets.destroy',$budget->id)])}} -{{Form::hidden('from',e(Input::get('from')))}} -
    -
    - @if($budget->transactionjournals()->count() > 0) -

    - - Account "{{{$budget->name}}}" still has {{$budget->transactionjournals()->count()}} transaction(s) associated to it. - These will NOT be deleted but will lose their connection to the budget. -

    - @endif - -

    - Press "Delete permanently" If you are sure you want to delete "{{{$budget->name}}}". -

    -
    - -
    -
    - - @if(Input::get('from') == 'date') - Cancel - @else - Cancel - @endif +
    From 44705f0e1866e5c8f3e6bd6aacff47158ae8738b Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Thu, 6 Nov 2014 20:33:37 +0100 Subject: [PATCH 45/57] Some bugfixes and cleanup. --- app/controllers/AccountController.php | 2 +- app/controllers/BudgetController.php | 4 +- app/lib/FireflyIII/Database/Budget.php | 7 +- app/views/budgets/indexByBudget.blade.php | 113 ---------------------- app/views/budgets/indexByDate.blade.php | 87 ----------------- app/views/budgets/nobudget.blade.php | 30 ------ app/views/budgets/show.blade.php | 110 --------------------- 7 files changed, 8 insertions(+), 345 deletions(-) delete mode 100644 app/views/budgets/indexByBudget.blade.php delete mode 100644 app/views/budgets/indexByDate.blade.php delete mode 100644 app/views/budgets/nobudget.blade.php diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 8e10e9f830..ba394611ae 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -382,7 +382,7 @@ class AccountController extends BaseController if ($data['post_submit_action'] == 'create_another') { return Redirect::route('accounts.edit', $account->id); } else { - return Redirect::route('accounts.edit', $account->id); + return Redirect::route('accounts.index',$data['what']); } case 'validate_only': $messageBags = $acct->validate($data); diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index 74ed304099..26c1b3ead2 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -335,12 +335,12 @@ class BudgetController extends BaseController } // store! $repos->update($budget, $data); - Session::flash('success', 'Account updated!'); + Session::flash('success', 'Budget updated!'); if ($data['post_submit_action'] == 'create_another') { return Redirect::route('budgets.edit', $budget->id); } else { - return Redirect::route('budgets.edit', $budget->id); + return Redirect::route('budgets.index'); } case 'validate_only': $messageBags = $repos->validate($data); diff --git a/app/lib/FireflyIII/Database/Budget.php b/app/lib/FireflyIII/Database/Budget.php index 7d82420abb..ba8d22fcf4 100644 --- a/app/lib/FireflyIII/Database/Budget.php +++ b/app/lib/FireflyIII/Database/Budget.php @@ -91,10 +91,13 @@ class Budget implements CUD, CommonDatabaseCalls, BudgetInterface } else { $errors->add('name', 'Name is mandatory'); } - $validator = \Validator::make([$model], \Budget::$rules); + $validator = \Validator::make($model, \Component::$rules); + if ($validator->invalid()) { - $errors->merge($errors); + $errors->merge($validator->errors()); } + + if(!$errors->has('name')) { $successes->add('name','OK'); } diff --git a/app/views/budgets/indexByBudget.blade.php b/app/views/budgets/indexByBudget.blade.php deleted file mode 100644 index b567fc482a..0000000000 --- a/app/views/budgets/indexByBudget.blade.php +++ /dev/null @@ -1,113 +0,0 @@ -@extends('layouts.default') -@section('content') -
    -
    -

    Use budgets to organize and limit your expenses.

    - -

    - Budgets are groups of expenses that reappear every [period]*. Examples could be "groceries", "bills" or - "drinks with friends". The table below lists all of the budgets you have, if any. - By definition, budgets are an estimated amount - of money, ie. € 400,-. Such an estimation can change over time but should always be set. Budgets - without an actual budget are fairly pointless. -

    -

    - Use this page to create or change budgets and the estimated amount of money you think is wise. Pages further ahead - will explain what an "envelope" is in the context of budgeting. -

    -

    - * Every month, week, year, etc. -

    - - -

    -
    -
    -
    -
    - - - - - - - @foreach($budgets as $budget) - - - - - - @endforeach - -
    BudgetCurrent envelope(s)Update budget
    - {{{$budget->name}}} - - -
    -
    - Envelope -
    -
    - Left -
    -
    - @foreach($budget->limits as $limit) - @foreach($limit->limitrepetitions as $index => $rep) -
    -
    - - - {{mf($rep->amount,false)}} -
    -
    - @if($rep->leftInRepetition() < 0) - - - {{mf($rep->leftInRepetition(),false)}} - @else - - - {{mf($rep->leftInRepetition(),false)}} - @endif -
    - - @if($limit->repeats == 1) -
    - auto repeats -
    - @endif -
    -
    - - @if($limit->repeats == 0 || ($limit->repeats == 1 && $index == 0)) - - @endif -
    -
    -
    - @endforeach - @endforeach -

    - Add another envelope -

    -
    -
    - - - -
    -
    - -
    -
    -@stop \ No newline at end of file diff --git a/app/views/budgets/indexByDate.blade.php b/app/views/budgets/indexByDate.blade.php deleted file mode 100644 index 705dbb7105..0000000000 --- a/app/views/budgets/indexByDate.blade.php +++ /dev/null @@ -1,87 +0,0 @@ -@extends('layouts.default') -@section('content') -
    -
    -

    Use budgets to organize and limit your expenses.

    - -

    - Budgets are groups of expenses that reappear every [period]*. Examples could be "groceries", "bills" or - "drinks with friends". The table below lists all of the budgets you have, if any. - By definition, budgets are an estimated amount - of money, ie. € 400,-. Such an estimation can change over time but should always be set. Budgets - without an actual budget are fairly pointless. -

    -

    - Use this page to create or change budgets and the estimated amount of money you think is wise. Pages further ahead - will explain what an "envelope" is in the context of budgeting. -

    -

    - * Every month, week, year, etc. -

    - -
    -
    - - - -@foreach($budgets as $date => $entry) -
    -
    -

    {{$entry['date']}} - Create a new envelope for {{$entry['date']}} -

    - - - - - - - - @foreach($entry['limitrepetitions'] as $index => $repetition) - - - - - - - -@endforeach -
    BudgetEnvelopeLeft 
    -
    - - -
    -
    - - {{{$repetition->limit->budget->name}}} - - - - {{mf($repetition->amount,false)}} - - @if($repetition->left < 0) - - - {{mf($repetition->left,false)}} - @else - - - {{mf($repetition->left,false)}} - @endif - -
    - - -
    - @if($repetition->limit->repeats == 1) - auto repeats - @endif -
    -
    -
    -@endforeach - -@stop \ No newline at end of file diff --git a/app/views/budgets/nobudget.blade.php b/app/views/budgets/nobudget.blade.php deleted file mode 100644 index 9f392c4eb8..0000000000 --- a/app/views/budgets/nobudget.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -@extends('layouts.default') -@section('content') -
    -
    - @if($view == 'session') - -

    - This view is filtered to only show transactions between {{Session::get('start')->format('d M Y')}} - and {{Session::get('end')->format('d M Y')}}. -

    - @endif -
    -
    -@if($transactions->count() > 0) -
    -
    - @include('lists.transactions',['journals' => $transactions,'sum' => true]) -
    -
    -@else -
    -
    -

    {{$repetition['date']}} -

    -

    No transactions

    -
    -
    -@endif - -@stop \ No newline at end of file diff --git a/app/views/budgets/show.blade.php b/app/views/budgets/show.blade.php index bc3de1a0ca..73fd354ac8 100644 --- a/app/views/budgets/show.blade.php +++ b/app/views/budgets/show.blade.php @@ -1,119 +1,9 @@ @extends('layouts.default') @section('content') -
    -
    -

    Budgets can help you cut back on spending.

    - @if($view == 1) - -

    - This view is filtered to show only the envelope from - {{{$repetitions[0]['limitrepetition']->periodShow()}}}, - which contains {{mf($repetitions[0]['limit']->amount,false)}}. -

    - - @endif - - - @if($view == 2) - -

    - This view is filtered to show transactions not in an envelope only. -

    - @endif - - @if($view == 3) - -

    - This view is filtered to only show transactions between {{Session::get('start')->format('d M Y')}} - and {{Session::get('end')->format('d M Y')}}. -

    - @endif - @if($view != 4) -

    - Reset the filter -

    - @endif - -
    -
    - -
    -
    -
    - @if($view == 1) -
    - @endif - - - @if($view == 2) -
    - @endif - - @if($view == 3) -
    - @endif - - @if($view == 4) -
    - @endif - -
    -
    - -@foreach($repetitions as $repetition) -@if(isset($repetition['journals']) && count($repetition['journals']) > 0) -
    -
    - - - @if($repetition['paginated'] == true) -

    - - {{$repetition['date']}} paginated

    - @else -

    - - {{$repetition['date']}} - -

    - {{mf($repetition['limit']->amount,false)}} - (left: {{mf($repetition['limitrepetition']->leftInRepetition(),false)}}) - @endif - - @if($repetition['paginated'] == true) - @include('paginated.transactions',['journals' => $repetition['journals'],'highlight' => $highlight]) - @else - @include('lists.transactions',['journals' => $repetition['journals'],'sum' => true,'highlight' => $highlight]) - @endif -
    -
    -@else -
    -
    -

    {{$repetition['date']}} -

    -

    No transactions

    -
    -
    -@endif -@endforeach @stop @section('scripts') -@if($view == 1) -{{HTML::script('assets/javascript/firefly/budgets/limit.js')}} -@endif -@if($view == 2) -{{HTML::script('assets/javascript/firefly/budgets/nolimit.js')}} -@endif - -@if($view == 3) -{{HTML::script('assets/javascript/firefly/budgets/session.js')}} -@endif -@if($view == 4) -{{HTML::script('assets/javascript/firefly/budgets/default.js')}} -@endif @stop \ No newline at end of file From 139d98590422a9d8610f292c934d01ab4147ac24 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Fri, 7 Nov 2014 11:18:06 +0100 Subject: [PATCH 46/57] All kinds of fixes and things. I should really start organizing. --- app/controllers/GoogleTableController.php | 51 ++++++ app/controllers/HomeController.php | 59 ++++++- app/lib/FireflyIII/Database/Account.php | 6 +- app/lib/FireflyIII/Database/AccountType.php | 1 - .../Database/TransactionJournal.php | 2 +- app/routes.php | 2 + app/views/accounts/asset.blade.php | 18 --- app/views/accounts/expense.blade.php | 28 ---- app/views/accounts/index.blade.php | 22 ++- app/views/accounts/list.blade.php | 26 --- app/views/accounts/revenue.blade.php | 28 ---- app/views/accounts/sankey.blade.php | 44 ------ public/assets/images/error.png | Bin 0 -> 666 bytes public/assets/javascript/firefly/accounts.js | 149 ++---------------- public/assets/javascript/firefly/gcharts.js | 14 +- public/assets/stylesheets/sbadmin/sb.css | 3 +- 16 files changed, 142 insertions(+), 311 deletions(-) delete mode 100644 app/views/accounts/asset.blade.php delete mode 100644 app/views/accounts/expense.blade.php delete mode 100644 app/views/accounts/list.blade.php delete mode 100644 app/views/accounts/revenue.blade.php delete mode 100644 app/views/accounts/sankey.blade.php create mode 100755 public/assets/images/error.png diff --git a/app/controllers/GoogleTableController.php b/app/controllers/GoogleTableController.php index 727b6af6fb..d464fdab02 100644 --- a/app/controllers/GoogleTableController.php +++ b/app/controllers/GoogleTableController.php @@ -1,11 +1,62 @@ getAssetAccounts(); + break; + case 'expense': + $list = $acct->getExpenseAccounts(); + break; + case 'revenue': + $list = $acct->getRevenueAccounts(); + break; + } + + + $chart = App::make('gchart'); + $chart->addColumn('ID', 'number'); + $chart->addColumn('ID_Edit', 'string'); + $chart->addColumn('ID_Delete', 'string'); + $chart->addColumn('Name_URL', 'string'); + $chart->addColumn('Name', 'string'); + $chart->addColumn('Balance', 'number'); + + /** @var \Account $entry */ + foreach ($list as $entry) { + $edit = route('accounts.edit', $entry->id); + $delete = route('accounts.delete', $entry->id); + $show = route('accounts.show', $entry->id); + $chart->addRow($entry->id, $edit, $delete, $show, $entry->name, $entry->balance()); + } + + $chart->generate(); + return Response::json($chart->getData()); + + + } + /** * @param Account $account */ diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php index 0f2a049c9d..ffd33b86cd 100644 --- a/app/controllers/HomeController.php +++ b/app/controllers/HomeController.php @@ -15,8 +15,8 @@ class HomeController extends BaseController protected $_journal; /** - * @param ARI $accounts - * @param PHI $preferences + * @param ARI $accounts + * @param PHI $preferences * @param TJRI $journal */ public function __construct(ARI $accounts, PHI $preferences, TJRI $journal) @@ -115,4 +115,59 @@ class HomeController extends BaseController return View::make('index')->with('count', $count)->with('transactions', $transactions)->with('title', 'Firefly') ->with('subTitle', 'What\'s playing?')->with('mainTitleIcon', 'fa-fire'); } + + public function cleanup() + { + /** @var \FireflyIII\Database\TransactionJournal $jrnls */ + $jrnls = App::make('FireflyIII\Database\TransactionJournal'); + + /** @var \FireflyIII\Database\Account $acct */ + $acct = \App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Database\AccountType $acctType */ + $acctType = \App::make('FireflyIII\Database\AccountType'); + $rightAcctType = $acctType->findByWhat('revenue'); + + $all = $jrnls->get(); + + /** @var \TransactionJournal $entry */ + foreach ($all as $entry) { + $wrongFromType = false; + $wrongToType = false; + $transactions = $entry->transactions; + if (count($transactions) == 2) { + switch ($entry->transactionType->type) { + case 'Deposit': + /** @var \Transaction $transaction */ + foreach ($transactions as $transaction) { + if (floatval($transaction->amount) < 0) { + $accountType = $transaction->account->accountType; + if ($accountType->type == 'Beneficiary account') { + // should be a Revenue account! + $name = $transaction->account->name; + /** @var \Account $account */ + $account = \Auth::user()->accounts()->where('name', $name)->where('account_type_id', $rightAcctType->id)->first(); + if (!$account) { + $new = [ + 'name' => $name, + 'what' => 'revenue' + ]; + $account = $acct->store($new); + } + $transaction->account()->associate($account); + $transaction->save(); + } + + echo 'Paid by: ' . $transaction->account->name . ' (' . $transaction->account->accountType->type . ')
    '; + } + } + break; + } + + + } + } + + + } } \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Account.php b/app/lib/FireflyIII/Database/Account.php index d683bf9b2c..60726af53c 100644 --- a/app/lib/FireflyIII/Database/Account.php +++ b/app/lib/FireflyIII/Database/Account.php @@ -123,11 +123,7 @@ class Account implements CUD, CommonDatabaseCalls, AccountInterface /* * If present, process parameters for sorting: */ - if (isset($parameters['order'])) { - foreach ($parameters['order'] as $instr) { - $query->orderBy($instr['name'], $instr['dir']); - } - } + $query->orderBy('name', 'ASC'); /* * If present, process parameters for searching. diff --git a/app/lib/FireflyIII/Database/AccountType.php b/app/lib/FireflyIII/Database/AccountType.php index a08b00f385..8a4e13f1dc 100644 --- a/app/lib/FireflyIII/Database/AccountType.php +++ b/app/lib/FireflyIII/Database/AccountType.php @@ -8,7 +8,6 @@ use LaravelBook\Ardent\Ardent; use FireflyIII\Database\Ifaces\AccountTypeInterface; use FireflyIII\Database\Ifaces\CommonDatabaseCalls; use FireflyIII\Database\Ifaces\CUD; -use FireflyIII\Database\Ifaces\AccountTypeInterface; /** * Class AccountType diff --git a/app/lib/FireflyIII/Database/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal.php index 5b37d5c2ae..ae07ca3dfe 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal.php @@ -289,7 +289,7 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData */ public function get() { - // TODO: Implement get() method. + return $this->getUser()->transactionjournals()->get(); } /** diff --git a/app/routes.php b/app/routes.php index 0444f2c99a..5a1b2b5223 100644 --- a/app/routes.php +++ b/app/routes.php @@ -128,6 +128,7 @@ Route::group( Route::get('/prev', ['uses' => 'HomeController@sessionPrev', 'as' => 'sessionPrev']); Route::get('/next', ['uses' => 'HomeController@sessionNext', 'as' => 'sessionNext']); Route::get('/jump/{range}', ['uses' => 'HomeController@rangeJump', 'as' => 'rangeJump']); + Route::get('/cleanup', ['uses' => 'HomeController@cleanup', 'as' => 'cleanup']); // account controller: Route::get('/accounts/json/{what}', ['uses' => 'AccountController@json', 'as' => 'accounts.json'])->where('what', 'revenue|asset|expense'); @@ -170,6 +171,7 @@ Route::group( // google table controller Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); + Route::get('/table/accounts/{what}', ['uses' => 'GoogleTableController@accountList']); Route::get('/chart/home/info/{accountnameA}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']); diff --git a/app/views/accounts/asset.blade.php b/app/views/accounts/asset.blade.php deleted file mode 100644 index b64988ce02..0000000000 --- a/app/views/accounts/asset.blade.php +++ /dev/null @@ -1,18 +0,0 @@ -@extends('layouts.default') -@section('content') -
    -
    -

    - Create a new asset account -

    - @if(count($accounts) > 0) - @include('accounts.list') -

    - Create a new asset account -

    - @endif -
    - -
    - -@stop \ No newline at end of file diff --git a/app/views/accounts/expense.blade.php b/app/views/accounts/expense.blade.php deleted file mode 100644 index 0002fba73e..0000000000 --- a/app/views/accounts/expense.blade.php +++ /dev/null @@ -1,28 +0,0 @@ -@extends('layouts.default') -@section('content') -
    -
    -

    - Bla bla expense -

    -

    - Bla bla bla expense -

    -
    -
    -
    -
    -

    - Create a new expense account -

    - @if(count($accounts) > 0) - @include('accounts.list') -

    - Create a new expense account -

    - @endif -
    - -
    - -@stop \ No newline at end of file diff --git a/app/views/accounts/index.blade.php b/app/views/accounts/index.blade.php index d67f0812e1..2410bc6c8e 100644 --- a/app/views/accounts/index.blade.php +++ b/app/views/accounts/index.blade.php @@ -22,29 +22,25 @@
    - - - - - - - - -
    NamebalanceID
    +
    @stop @section('scripts') -{{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} -{{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} + + + +{{HTML::script('assets/javascript/firefly/gcharts.options.js')}} +{{HTML::script('assets/javascript/firefly/gcharts.js')}} + + @stop @section('styles') -{{HTML::style('assets/stylesheets/datatables/dataTables.bootstrap.css')}} @endsection \ No newline at end of file diff --git a/app/views/accounts/list.blade.php b/app/views/accounts/list.blade.php deleted file mode 100644 index 52bef1acf3..0000000000 --- a/app/views/accounts/list.blade.php +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - @foreach($accounts as $account) - - - - - - - @endforeach -
     NameCurrent balance
    - @if($account->active == 0) - - @endif - - {{{$account->name}}}{{mf($account->balance())}} - - - - -
    \ No newline at end of file diff --git a/app/views/accounts/revenue.blade.php b/app/views/accounts/revenue.blade.php deleted file mode 100644 index c5a1aed311..0000000000 --- a/app/views/accounts/revenue.blade.php +++ /dev/null @@ -1,28 +0,0 @@ -@extends('layouts.default') -@section('content') -
    -
    -

    - Bla bla revenue -

    -

    - Bla bla bla revenue -

    -
    -
    -
    -
    -

    - Create a new revenue account -

    - @if(count($accounts) > 0) - @include('accounts.list') -

    - Create a new revenue account -

    - @endif -
    - -
    - -@stop \ No newline at end of file diff --git a/app/views/accounts/sankey.blade.php b/app/views/accounts/sankey.blade.php deleted file mode 100644 index 26beb8fe60..0000000000 --- a/app/views/accounts/sankey.blade.php +++ /dev/null @@ -1,44 +0,0 @@ -@extends('layouts.default') -@section('content') - - -
    - - - - -@stop -@section('scripts') - - -@stop \ No newline at end of file diff --git a/public/assets/images/error.png b/public/assets/images/error.png new file mode 100755 index 0000000000000000000000000000000000000000..628cf2dae3d419ae220c8928ac71393b480745a3 GIT binary patch literal 666 zcmV;L0%iS)P)eOSYYtbpBV}~vsBnU!_?2tr-P=|^T zED%wc9ezHgW@NMb!^uT_|SvCpFLJylbx zY%bpaTGI8IYXMN$9w<3j9VkA~NYOKEQXsj?6a9_hcwfU$acAhJhB)zb_w@MVUEy@S zX&I>K-R!bhu3?(6bHWIg$HEl7{9g>>&l_qdd+UYb(1~BCo9LptNq&8>!yoJ3Ui(i5 zRJ|XnYBklL!{@$-7=3mJ>P@1c=7Oc79e-V7yf+%lD2!I;Y&nXBZ>=B!5?CB>LvEx6 znI%n)qqi$#X#wKB(U7XP2P=+4{b@j#r%9-K(8UqtSDk>0UKzf*HM9yqMZ1D!$2MdZ zR=`U>0zhOH1XqN?nY@AQqB7)Fp4{v&dKXvb43hZKvnN8;Po;+jY*}~*Z|W9Q0W%{D z^T}Cc<|r(Su=1K=P5>Z4 zg`et&Va}tdzBS-G-ZcO)zCWpJvGQwrHZ`@wpM420ac@bI5~KkTFfGEM3sPWO8co4^fI6lPnA)Y{ef%@{+SnoUk0+dW+*{8WvF8}}l07*qoM6N<$g7cXs A&j0`b literal 0 HcmV?d00001 diff --git a/public/assets/javascript/firefly/accounts.js b/public/assets/javascript/firefly/accounts.js index 702335e3ae..2387946845 100644 --- a/public/assets/javascript/firefly/accounts.js +++ b/public/assets/javascript/firefly/accounts.js @@ -1,145 +1,20 @@ $(function () { - if (typeof(googleLineChart) == "function") { + if (typeof(googleLineChart) == "function" && typeof accountID != 'undefined') { googleLineChart('chart/account/' + accountID, 'overview-chart'); } // - if(typeof(googleSankeyChart) == 'function') { - googleSankeyChart('chart/sankey/' + accountID + '/out','account-out-sankey'); - googleSankeyChart('chart/sankey/' + accountID + '/in','account-in-sankey'); + if (typeof(googleSankeyChart) == 'function' && typeof accountID != 'undefined') { + googleSankeyChart('chart/sankey/' + accountID + '/out', 'account-out-sankey'); + googleSankeyChart('chart/sankey/' + accountID + '/in', 'account-in-sankey'); } - if(typeof(googleTable) == 'function') { - googleTable('table/account/' + accountID + '/transactions','account-transactions'); + if (typeof(googleTable) == 'function') { + if (typeof accountID != 'undefined') { + googleTable('table/account/' + accountID + '/transactions', 'account-transactions'); + } + if (typeof what != 'undefined') { + googleTable('table/accounts/' + what, 'account-list'); + } } - - if ($('#accountTable').length == 1) { - drawDatatable(); - } - //if ($('#overviewChart').length == 1) { - // drawOverviewChart(); - //} - -}); - -function drawDatatable() { - var opt = { - serverSide: true, - ajax: URL, - paging: true, - processing: true, - columns: [ - { - name: 'name', - data: 'name', - searchable: true, - render: function (data) { - return '' + data.name + ''; - } - - }, - { - name: 'balance', - data: 'balance', - title: 'Amount (\u20AC)', - searchable: false, - sortable: true, - render: function (data) { - var amount = parseInt(data); - if (amount < 0) { - '\u20AC ' + data.toFixed(2) + '' - } - if (amount > 0) { - '\u20AC ' + data.toFixed(2) + '' - } - return '\u20AC ' + data.toFixed(2) + '' - } - }, - { - name: 'id', - data: 'id', - title: '', - render: function (data) { - return ''; - } - } - ] - }; - $('#accountTable').DataTable(opt); -} - - -function drawOverviewChart() { - $.getJSON('chart/home/account/' + accountID).success(function (data) { - var options = { - chart: { - renderTo: 'overviewChart', - type: 'spline' - }, - - series: data.series, - title: { - text: null - }, - yAxis: { - allowDecimals: false, - labels: { - formatter: function () { - if (this.value >= 1000 || this.value <= -1000) { - return '\u20AC ' + (this.value / 1000) + 'k'; - } - return '\u20AC ' + this.value; - - } - }, - title: {text: null} - }, - xAxis: { - type: 'datetime', - dateTimeLabelFormats: { - day: '%e %b', - week: '%e %b' - }, - title: { - text: null - } - }, - legend: {enabled: false}, - tooltip: { - formatter: function () { - return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); - } - }, - plotOptions: { - line: { - shadow: true - }, - series: { - cursor: 'pointer', - negativeColor: '#FF0000', - threshold: 0, - lineWidth: 1, - marker: { - radius: 0 - }, - point: { - events: { - click: function (e) { - alert('click!'); - } - } - } - } - }, - credits: { - enabled: false - } - }; - $('#overviewChart').highcharts(options); - }); -} +}); \ No newline at end of file diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js index 367b017e9c..9b2f2c3940 100644 --- a/public/assets/javascript/firefly/gcharts.js +++ b/public/assets/javascript/firefly/gcharts.js @@ -1,4 +1,4 @@ -google.load('visualization', '1.1', {'packages': ['corechart', 'bar','sankey', 'table']}); +google.load('visualization', '1.1', {'packages': ['corechart', 'bar', 'sankey', 'table']}); function googleLineChart(URL, container) { $.getJSON(URL).success(function (data) { @@ -187,24 +187,24 @@ function googleTable(URL, container) { /* Format a string using the previous column as URL. */ - if (label == 'Description' || label == 'From' || label == 'To' || label == 'Budget' || label == 'Category') { + if (label == 'Description' || label == 'From' || label == 'Name' || label == 'To' || label == 'Budget' || label == 'Category') { URLFormatter.format(gdata, [i - 1, i], i); columnsToHide.push(i - 1); } - if(label == 'ID') { - EditButtonFormatter.format(gdata, [i+1,i+2],i); - columnsToHide.push(i+1,i+2); + if (label == 'ID') { + EditButtonFormatter.format(gdata, [i + 1, i + 2], i); + columnsToHide.push(i + 1, i + 2); } /* - Format with buttons: + Format with buttons: */ /* Format as money */ - if (label == 'Amount') { + if (label == 'Amount' || label == 'Balance') { money.format(gdata, i); } diff --git a/public/assets/stylesheets/sbadmin/sb.css b/public/assets/stylesheets/sbadmin/sb.css index 2e181df17b..7317b94229 100755 --- a/public/assets/stylesheets/sbadmin/sb.css +++ b/public/assets/stylesheets/sbadmin/sb.css @@ -9,7 +9,8 @@ body { } .google-chart-error { - border:1px red solid;height:20px; + height:30px; + background:url('../../images/error.png') no-repeat center center } #wrapper { From ab4f34a96b060870de65cf471d8572f8798a9148 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Fri, 7 Nov 2014 22:06:30 +0100 Subject: [PATCH 47/57] Extended reports --- app/controllers/GoogleChartController.php | 51 +++++++++++++++++++ app/routes.php | 1 + app/views/reports/year.blade.php | 19 +++++-- public/assets/javascript/firefly/gcharts.js | 29 +++++++++++ .../javascript/firefly/gcharts.options.js | 38 +++++++++++++- public/assets/javascript/firefly/reports.js | 1 + 6 files changed, 134 insertions(+), 5 deletions(-) diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index 571ec2fb69..fb76aecc18 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -124,15 +124,19 @@ class GoogleChartController extends BaseController $end->endOfYear(); $income = 0; $expense = 0; + $count = 0; while ($start < $end) { // total income: $income += $tj->getSumOfIncomesByMonth($start); $expense += $tj->getSumOfExpensesByMonth($start); + $count++; $start->addMonth(); } $chart->addRow('Sum', $income, $expense); + $count = $count > 0 ? $count : 1; + $chart->addRow('Average', ($income / $count), ($expense / $count)); $chart->generate(); @@ -140,6 +144,53 @@ class GoogleChartController extends BaseController } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function budgetsReportChart($year) + { + + try { + $start = new Carbon('01-01-' . $year); + } catch (Exception $e) { + App::abort(500); + } + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + + /** @var \FireflyIII\Database\Budget $bdt */ + $bdt = App::make('FireflyIII\Database\Budget'); + $budgets = $bdt->get(); + + $chart->addColumn('Month', 'date'); + /** @var \Budget $budget */ + foreach ($budgets as $budget) { + $chart->addColumn($budget->name, 'number'); + } + + /* + * Loop budgets this year. + */ + $end = clone $start; + $end->endOfYear(); + while ($start <= $end) { + $row = [clone $start]; + + foreach($budgets as $budget) { + $row[] = $bdt->spentInMonth($budget, $start); + } + + $chart->addRowArray($row); + $start->addMonth(); + } + + + $chart->generate(); + return Response::json($chart->getData()); + } + /** * @return \Illuminate\Http\JsonResponse */ diff --git a/app/routes.php b/app/routes.php index 5a1b2b5223..72486627ee 100644 --- a/app/routes.php +++ b/app/routes.php @@ -168,6 +168,7 @@ Route::group( Route::get('/chart/sankey/{account}/in', ['uses' => 'GoogleChartController@accountSankeyInChart']); Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']); Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']); + Route::get('/chart/reports/budgets/{year}', ['uses' => 'GoogleChartController@budgetsReportChart']); // google table controller Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); diff --git a/app/views/reports/year.blade.php b/app/views/reports/year.blade.php index 9ab2207b83..ffbd8ab0cc 100644 --- a/app/views/reports/year.blade.php +++ b/app/views/reports/year.blade.php @@ -25,7 +25,7 @@
    -
    +
    Summary
    @@ -33,8 +33,8 @@ - @foreach($summary as $entry) - + @foreach($summary as $entry) + @endforeach @@ -67,6 +67,19 @@ +
    +
    +
    +
    + Budgets +
    +
    +
    +
    +
    +
    +
    + @stop @section('scripts') diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js index 9b2f2c3940..b17ab935e1 100644 --- a/public/assets/javascript/firefly/gcharts.js +++ b/public/assets/javascript/firefly/gcharts.js @@ -89,6 +89,35 @@ function googleColumnChart(URL, container) { }); } +function googleStackedColumnChart(URL, container) { + $.getJSON(URL).success(function (data) { + /* + Get the data from the JSON + */ + gdata = new google.visualization.DataTable(data); + + /* + Format as money + */ + var money = new google.visualization.NumberFormat({decimalSymbol: ',', groupingSymbol: '.', prefix: '\u20AC '}); + for (i = 1; i < gdata.getNumberOfColumns(); i++) { + money.format(gdata, i); + } + + /* + Create a new google charts object. + */ + var chart = new google.visualization.ColumnChart(document.getElementById(container)); + /* + Draw it: + */ + chart.draw(gdata, defaultStackedColumnChartOptions); + + }).fail(function () { + $('#' + container).addClass('google-chart-error'); + }); +} + function googlePieChart(URL, container) { $.getJSON(URL).success(function (data) { /* diff --git a/public/assets/javascript/firefly/gcharts.options.js b/public/assets/javascript/firefly/gcharts.options.js index 71e8193561..db138b0c14 100644 --- a/public/assets/javascript/firefly/gcharts.options.js +++ b/public/assets/javascript/firefly/gcharts.options.js @@ -12,7 +12,6 @@ var defaultLineChartOptions = { height: '80%' }, height: 400, - vAxis: {format: '\u20AC #'}, colors: ["#4285f4", "#db4437", "#f4b400", "#0f9d58", "#ab47bc", "#00acc1", "#ff7043", "#9e9d24", "#5c6bc0", "#f06292", "#00796b", "#c2185b"], hAxis: { textStyle: { @@ -29,7 +28,8 @@ var defaultLineChartOptions = { color: '#838383', fontName: 'Roboto2', fontSize: '12' - } + }, + format: '\u20AC #' } @@ -65,6 +65,40 @@ var defaultColumnChartOptions = { }, }; +var defaultStackedColumnChartOptions = { + height: 400, + chartArea: { + left: 50, + top: 10, + width: '85%', + height: '80%' + }, + vAxis: {format: '\u20AC #'}, + legend: { + position: 'none' + }, + isStacked: true, + colors: ["#4285f4", "#db4437", "#f4b400", "#0f9d58", "#ab47bc", "#00acc1", "#ff7043", "#9e9d24", "#5c6bc0", "#f06292", "#00796b", "#c2185b"], + vAxis: { + textStyle: { + color: '#838383', + fontName: 'Roboto2', + fontSize: '12' + }, + format: '\u20AC #' + }, + hAxis: { + textStyle: { + color: '#838383', + fontName: 'Roboto2', + fontSize: '12' + }, + gridlines: { + color: 'transparent' + } + }, +}; + var defaultPieChartOptions = { chartArea: { left: 0, diff --git a/public/assets/javascript/firefly/reports.js b/public/assets/javascript/firefly/reports.js index 445304a04c..6977f38f57 100644 --- a/public/assets/javascript/firefly/reports.js +++ b/public/assets/javascript/firefly/reports.js @@ -4,4 +4,5 @@ google.setOnLoadCallback(drawChart); function drawChart() { googleColumnChart('chart/reports/income-expenses/' + year, 'income-expenses-chart'); googleColumnChart('chart/reports/income-expenses-sum/' + year, 'income-expenses-sum-chart') + googleStackedColumnChart('chart/reports/budgets/' + year, 'budgets'); } \ No newline at end of file From 6d8f84654f26761109082e82c30dc024a6c07eec Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sat, 8 Nov 2014 09:15:03 +0100 Subject: [PATCH 48/57] Also add stuff not in budgets. --- app/controllers/GoogleChartController.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index fb76aecc18..b72d791379 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -169,6 +169,7 @@ class GoogleChartController extends BaseController foreach ($budgets as $budget) { $chart->addColumn($budget->name, 'number'); } + $chart->addColumn('No budget','number'); /* * Loop budgets this year. @@ -182,6 +183,14 @@ class GoogleChartController extends BaseController $row[] = $bdt->spentInMonth($budget, $start); } + /* + * Without a budget: + */ + $endOfMonth = clone $start; + $endOfMonth->endOfMonth(); + $set = $bdt->transactionsWithoutBudgetInDateRange($start, $endOfMonth); + $row[] = floatval($set->sum('amount')) * -1; + $chart->addRowArray($row); $start->addMonth(); } From 8196313ac0f8dffb6d3b021afc7c7aaf784731a1 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sat, 8 Nov 2014 10:16:12 +0100 Subject: [PATCH 49/57] Optimized queries. --- .../Database/TransactionJournal.php | 33 +++++++++++-------- app/models/TransactionJournal.php | 2 ++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/app/lib/FireflyIII/Database/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal.php index ae07ca3dfe..3355f458a8 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal.php @@ -40,12 +40,15 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData $end = clone $date; $date->startOfMonth(); $end->endOfMonth(); - $list = $this->getUser()->transactionjournals()->transactionTypes(['Deposit'])->before($end)->after($date)->get(['transaction_journals.*']); - $sum = 0; - /** @var \TransactionJournal $entry */ - foreach ($list as $entry) { - $sum += $entry->getAmount(); - } + + $sum = \DB::table('transactions') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->where('amount', '>', 0) + ->where('transaction_types.type', '=', 'Deposit') + ->where('transaction_journals.date', '>=', $date->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('transactions.amount'); + $sum = floatval($sum); return $sum; } @@ -54,16 +57,20 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData * * @return float */ - public function getSumOfExpensesByMonth(Carbon $date) { + public function getSumOfExpensesByMonth(Carbon $date) + { $end = clone $date; $date->startOfMonth(); $end->endOfMonth(); - $list = $this->getUser()->transactionjournals()->transactionTypes(['Withdrawal'])->before($end)->after($date)->get(['transaction_journals.*']); - $sum = 0; - /** @var \TransactionJournal $entry */ - foreach ($list as $entry) { - $sum += $entry->getAmount(); - } + + $sum = \DB::table('transactions') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->where('amount', '>', 0) + ->where('transaction_types.type', '=', 'Withdrawal') + ->where('transaction_journals.date', '>=', $date->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('transactions.amount'); + $sum = floatval($sum); return $sum; } diff --git a/app/models/TransactionJournal.php b/app/models/TransactionJournal.php index 08808110e4..fb760e6782 100644 --- a/app/models/TransactionJournal.php +++ b/app/models/TransactionJournal.php @@ -50,11 +50,13 @@ class TransactionJournal extends Ardent */ public function getAmount() { + foreach ($this->transactions as $t) { if (floatval($t->amount) > 0) { return floatval($t->amount); } } + return -0.01; } /** From fe05d218fcde1eba7de9316b3c51f392795224ab Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sat, 8 Nov 2014 11:36:20 +0100 Subject: [PATCH 50/57] Allow piggy bank edit/update. --- app/controllers/PiggybankController.php | 67 ++++++++++++++--------- app/lib/FireflyIII/Database/Piggybank.php | 16 +++++- app/views/piggybanks/edit.blade.php | 2 +- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index aa008c0088..623072abe8 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -529,34 +529,49 @@ class PiggybankController extends BaseController // // } -// /** -// * @param Piggybank $piggyBank -// * -// * @return $this|\Illuminate\Http\RedirectResponse -// */ + /** + * @param Piggybank $piggyBank + * + * @return $this|\Illuminate\Http\RedirectResponse + */ public function update(Piggybank $piggyBank) { - throw new NotImplementedException; -// $piggyBank = $this->_repository->update($piggyBank, Input::all()); -// if ($piggyBank->validate()) { -// if ($piggyBank->repeats == 1) { -// $route = 'piggybanks.index.repeated'; -// $message = 'Repeated expense'; -// } else { -// $route = 'piggybanks.index.piggybanks'; -// $message = 'Piggy bank'; -// } -// -// -// Session::flash('success', $message . ' "' . $piggyBank->name . '" updated.'); -// Event::fire('piggybanks.update', [$piggyBank]); -// -// return Redirect::route($route); -// } else { -// Session::flash('error', 'Could not update piggy bank: ' . $piggyBank->errors()->first()); -// -// return Redirect::route('piggybanks.edit', $piggyBank->id)->withErrors($piggyBank->errors())->withInput(); -// } + + /** @var \FireflyIII\Database\Piggybank $repos */ + $repos = App::make('FireflyIII\Database\Piggybank'); + $data = Input::except('_token'); + + switch (Input::get('post_submit_action')) { + default: + throw new FireflyException('Cannot handle post_submit_action "' . e(Input::get('post_submit_action')) . '"'); + break; + case 'create_another': + case 'update': + $messages = $repos->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save piggy bank: ' . $messages['errors']->first()); + return Redirect::route('piggybanks.edit', $piggyBank->id)->withInput()->withErrors($messages['errors']); + } + // store! + $repos->update($piggyBank, $data); + Session::flash('success', 'Piggy bank updated!'); + + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('piggybanks.edit', $piggyBank->id); + } else { + return Redirect::route('piggybanks.index'); + } + case 'validate_only': + $messageBags = $repos->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); + return Redirect::route('piggybanks.edit', $piggyBank->id)->withInput(); + break; + } } diff --git a/app/lib/FireflyIII/Database/Piggybank.php b/app/lib/FireflyIII/Database/Piggybank.php index 9f34185387..f99aa14040 100644 --- a/app/lib/FireflyIII/Database/Piggybank.php +++ b/app/lib/FireflyIII/Database/Piggybank.php @@ -231,6 +231,20 @@ class Piggybank implements CUD, CommonDatabaseCalls, PiggybankInterface */ public function update(Ardent $model, array $data) { - // TODO: Implement update() method. + /** @var \Piggybank $model */ + $model->name = $data['name']; + $model->account_id = intval($data['account_id']); + $model->targetamount = floatval($data['targetamount']); + $model->targetdate = isset($data['targetdate']) && $data['targetdate'] != '' ? $data['targetdate'] : null; + $model->rep_every = isset($data['rep_every']) ? $data['rep_every'] : 0; + $model->reminder_skip = isset($data['reminder_skip']) ? $data['reminder_skip'] : 0; + $model->order = isset($data['order']) ? $data['order'] : 0; + $model->remind_me = isset($data['remind_me']) ? intval($data['remind_me']) : 0; + if(!$model->validate()) { + var_dump($model->errors()); + exit(); + } + $model->save(); + return true; } } \ No newline at end of file diff --git a/app/views/piggybanks/edit.blade.php b/app/views/piggybanks/edit.blade.php index f46f1fadce..e8ae4435ac 100644 --- a/app/views/piggybanks/edit.blade.php +++ b/app/views/piggybanks/edit.blade.php @@ -40,7 +40,7 @@ Options
    - {{Form::ffOptionsList('create','piggy bank')}} + {{Form::ffOptionsList('update','piggy bank')}}
    From 7895d7f5d08ee9d98085a203227e06725c365a34 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sat, 8 Nov 2014 11:37:55 +0100 Subject: [PATCH 51/57] Format amount. --- app/views/piggybanks/add.blade.php | 2 +- app/views/piggybanks/remove.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/piggybanks/add.blade.php b/app/views/piggybanks/add.blade.php index 6b59827d2f..4320379029 100644 --- a/app/views/piggybanks/add.blade.php +++ b/app/views/piggybanks/add.blade.php @@ -12,7 +12,7 @@

    - +
    +
    +
    + Spent: {{mf($spent)}} +
    +
    +
    +
    +
    + @if($overspent) +
    +
    + @else +
    + @endif +
    +
    +
    From f69b6f9b4edfd892737f9fb8e139ddd41b331712 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 9 Nov 2014 08:42:09 +0100 Subject: [PATCH 55/57] New chart for budget-overview. --- app/controllers/GoogleChartController.php | 41 +++++++++++++++++++++ app/routes.php | 1 + app/views/budgets/show.blade.php | 6 ++- public/assets/javascript/firefly/budgets.js | 2 + 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index b72d791379..daeedcfddd 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -200,6 +200,47 @@ class GoogleChartController extends BaseController return Response::json($chart->getData()); } + public function budgetsAndSpending(Budget $budget, $year) { + try { + $start = new Carbon('01-01-' . $year); + } catch (Exception $e) { + App::abort(500); + } + + /** @var \FireflyIII\Database\Budget $bdt */ + $bdt = App::make('FireflyIII\Database\Budget'); + + /** @var \Grumpydictator\Gchart\GChart $chart */ + $chart = App::make('gchart'); + $chart->addColumn('Month', 'date'); + $chart->addColumn('Budgeted', 'number'); + $chart->addColumn('Spent', 'number'); + + $end = clone $start; + $end->endOfYear(); + while($start <= $end) { + + $spent = $bdt->spentInMonth($budget, $start); + $repetition = $bdt->repetitionOnStartingOnDate($budget, $start); + if($repetition) { + $budgeted = floatval($repetition->amount); + } else { + $budgeted = 0; + } + + $chart->addRow(clone $start, $budgeted, $spent); + + $start->addMonth(); + } + + + + $chart->generate(); + return Response::json($chart->getData()); + + + } + /** * @return \Illuminate\Http\JsonResponse */ diff --git a/app/routes.php b/app/routes.php index ff19a498ee..4d85b37703 100644 --- a/app/routes.php +++ b/app/routes.php @@ -169,6 +169,7 @@ Route::group( Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']); Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']); Route::get('/chart/reports/budgets/{year}', ['uses' => 'GoogleChartController@budgetsReportChart']); + Route::get('/chart/budgets/{budget}/spending/{year}', ['uses' => 'GoogleChartController@budgetsAndSpending']); // google table controller Route::get('/table/account/{account}/transactions', ['uses' => 'GoogleTableController@transactionsByAccount']); diff --git a/app/views/budgets/show.blade.php b/app/views/budgets/show.blade.php index 71d6418e42..2cdc0f3e14 100644 --- a/app/views/budgets/show.blade.php +++ b/app/views/budgets/show.blade.php @@ -8,7 +8,7 @@ Some stuff?
    - Some stuff? +
    @@ -76,7 +76,11 @@ var budgetID = {{$budget->id}}; @if(!is_null($repetition)) var repetitionID = {{$repetition->id}}; + var year = {{$repetition->startdate->format('Y')}}; + @else + var year = {{Session::get('start')->format('Y')}}; @endif + diff --git a/public/assets/javascript/firefly/budgets.js b/public/assets/javascript/firefly/budgets.js index 881948aa16..a6de8b79cc 100644 --- a/public/assets/javascript/firefly/budgets.js +++ b/public/assets/javascript/firefly/budgets.js @@ -10,6 +10,8 @@ $(function () { if (typeof(googleTable) == 'function') { if (typeof budgetID != 'undefined' && typeof repetitionID == 'undefined') { googleTable('table/budget/' + budgetID + '/0/transactions', 'transactions'); + googleColumnChart('chart/budgets/'+budgetID+'/spending/2014','budgetOverview'); + } else if (typeof budgetID != 'undefined' && typeof repetitionID != 'undefined') { googleTable('table/budget/' + budgetID + '/' + repetitionID + '/transactions', 'transactions'); } From c58b653bb721ecb9c8817a5ca4acb5ab834bf1a6 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 9 Nov 2014 11:01:57 +0100 Subject: [PATCH 56/57] Updated read me file. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35ed614ab1..e08e2c244d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Everything is organised: - Clear views that should show you how you're doing; - Easy navigation through your records; - Browse back and forth to see previous months or even years; -- Lots of help text in case you don't get it; - Lots of charts because we all love them. ## Changes @@ -36,6 +35,7 @@ Everything is organised: Firefly III will feature, but does not feature yet: - Financial reporting showing you how well you are doing; +- Lots of help text in case you don't get it; - More control over other resources outside of personal finance - Accounts shared with a partner (household accounts) - Debts From 0f6008705c251bdebd769b7429de322d2a530f0e Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 9 Nov 2014 11:08:53 +0100 Subject: [PATCH 57/57] Added a screenshot --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e08e2c244d..d44acfefd3 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ Some stuff has been removed: - The nesting of budgets, categories and beneficiaries is removed because it was pretty pointless. - Firefly will not encrypt the content of the (MySQL) tables. Old versions of Firefly had this capability but it sucks when searching, sorting and organizing entries. +## Screenshots + +![Index](http://i.imgur.com/oUQ1UhU.png) + ## Current state I have the basics up and running. Test coverage is currently non-existent.
    {{$entry['month']}}{{$entry['month']}}Sum