diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000000..8951ac7b73 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +src_dir: . +coverage_clover: tests/_output/coverage.xml +json_path: tests/_output/coveralls-upload.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..102eb830b2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: php + +php: + - 5.5 + - 5.6 + +addons: + code_climate: + repo_token: 26489f9e854fcdf7e7660ba29c1455694685465b1f90329a79f7d2bf448acb61 + +install: + - rm composer.lock + - composer install + +script: + - ./tests/_data/db.sh + - php vendor/bin/codecept build + - php vendor/bin/codecept run --coverage --coverage-xml + +after_script: + - cp -v tests/_output/coverage.xml build/logs/clover.xml + - php vendor/bin/coveralls + - vendor/bin/test-reporter --stdout > codeclimate.json + - "curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v0.1.1)' https://codeclimate.com/test_reports" \ No newline at end of file diff --git a/app/Http/Controllers/GoogleChartController.php b/app/Http/Controllers/GoogleChartController.php new file mode 100644 index 0000000000..9fe515eeef --- /dev/null +++ b/app/Http/Controllers/GoogleChartController.php @@ -0,0 +1,135 @@ +addColumn('Day of the month', 'date'); + + $frontPage = Preferences::get('frontPageAccounts', []); + $start = Session::get('start', Carbon::now()->startOfMonth()); + $end = Session::get('end', Carbon::now()->endOfMonth()); + + if ($frontPage->data == []) { + $accounts = Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->get(['accounts.*']); + } else { + $accounts = Auth::user()->accounts()->whereIn('id', $frontPage->data)->get(['accounts.*']); + } + $index = 1; + /** @var Account $account */ + foreach ($accounts as $account) { + $chart->addColumn('Balance for ' . $account->name, 'number'); + $chart->addCertainty($index); + $index++; + } + $current = clone $start; + $current->subDay(); + $today = Carbon::now(); + while ($end >= $current) { + $row = [clone $current]; + $certain = $current < $today; + foreach ($accounts as $account) { + $row[] = Steam::balance($account, $current); + $row[] = $certain; + } + $chart->addRowArray($row); + $current->addDay(); + } + $chart->generate(); + + return Response::json($chart->getData()); + + } + + + /** + * @param GChart $chart + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function allBudgetsHomeChart(GChart $chart) + { + $chart->addColumn('Budget', 'string'); + $chart->addColumn('Budgeted', 'number'); + $chart->addColumn('Spent', 'number'); + + $budgets = Auth::user()->budgets()->orderBy('name', 'DESC')->get(); + $start = Session::get('start', Carbon::now()->startOfMonth()); + $end = Session::get('end', Carbon::now()->endOfMonth()); + + /** @var Budget $budget */ + foreach ($budgets as $budget) { + + /** @var \LimitRepetition $repetition */ + $repetition = LimitRepetition:: + leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + ->where('limit_repetitions.startdate', $start->format('Y-m-d 00:00:00')) + ->where('budget_limits.budget_id', $budget->id) + ->first(['limit_repetitions.*']); + if (is_null($repetition)) { // use the session start and end for our search query + $searchStart = $start; + $searchEnd = $end; + $limit = 0; // the limit is zero: + } else { + // use the limit's start and end for our search query + $searchStart = $repetition->startdate; + $searchEnd = $repetition->enddate; + $limit = floatval($repetition->amount); // the limit is the repetitions limit: + } + + $expenses = floatval($budget->transactionjournals()->before($searchEnd)->after($searchStart)->lessThan(0)->sum('amount')) * -1; + if ($expenses > 0) { + $chart->addRow($budget->name, $limit, $expenses); + } + } + + $noBudgetSet = Auth::user() + ->transactionjournals() + ->whereNotIn( + 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { + $query + ->select('transaction_journals.id') + ->from('transaction_journals') + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')); + } + ) + ->before($end) + ->after($start) + ->lessThan(0) + ->transactionTypes(['Withdrawal']) + ->get(); + $sum = $noBudgetSet->sum('amount') * -1; + $chart->addRow('No budget', 0, $sum); + $chart->generate(); + + return Response::json($chart->getData()); + } + + +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 7eaa83087c..41fb237c2b 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,6 +1,6 @@ 'HomeController@index', 'as' => 'index']); @@ -8,7 +8,7 @@ Route::get('/prev', ['uses' => 'HomeController@sessionPrev', 'as' => 'sessionPre Route::get('/next', ['uses' => 'HomeController@sessionNext', 'as' => 'sessionNext']); Route::get('/jump/{range}', ['uses' => 'HomeController@rangeJump', 'as' => 'rangeJump']); -/* +/** * Account Controller */ Route::get('/accounts/{what}', ['uses' => 'AccountController@index', 'as' => 'accounts.index'])->where('what', 'revenue|asset|expense'); @@ -17,10 +17,9 @@ Route::get('/accounts/create/{what}', ['uses' => 'AccountController@create', 'as //Route::get('/accounts/delete/{account}', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']); Route::get('/accounts/show/{account}/{view?}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); -/* +/** * Bills Controller */ -// bills controller Route::get('/bills', ['uses' => 'BillController@index', 'as' => 'bills.index']); //Route::get('/bills/rescan/{bill}', ['uses' => 'BillController@rescan', 'as' => 'bills.rescan']); # rescan for matching. Route::get('/bills/create', ['uses' => 'BillController@create', 'as' => 'bills.create']); @@ -28,7 +27,7 @@ Route::get('/bills/create', ['uses' => 'BillController@create', 'as' => 'bills.c //Route::get('/bills/delete/{bill}', ['uses' => 'BillController@delete', 'as' => 'bills.delete']); //Route::get('/bills/show/{bill}', ['uses' => 'BillController@show', 'as' => 'bills.show']); -/* +/** * Budget Controller */ Route::get('/budgets', ['uses' => 'BudgetController@index', 'as' => 'budgets.index']); @@ -39,7 +38,7 @@ Route::get('/budgets', ['uses' => 'BudgetController@index', 'as' => 'budgets.ind //Route::get('/budgets/show/{budget}/{limitrepetition?}', ['uses' => 'BudgetController@show', 'as' => 'budgets.show']); //Route::get('/budgets/list/noBudget', ['uses' => 'BudgetController@noBudget', 'as' => 'budgets.noBudget']); -/* +/** * Category Controller */ Route::get('/categories', ['uses' => 'CategoryController@index', 'as' => 'categories.index']); @@ -49,7 +48,7 @@ Route::get('/categories', ['uses' => 'CategoryController@index', 'as' => 'catego //Route::get('/categories/show/{category}', ['uses' => 'CategoryController@show', 'as' => 'categories.show']); //Route::get('/categories/list/noCategory', ['uses' => 'CategoryController@noCategory', 'as' => 'categories.noCategory']); -/* +/** * Currency Controller */ Route::get('/currency', ['uses' => 'CurrencyController@index', 'as' => 'currency.index']); @@ -58,7 +57,20 @@ Route::get('/currency', ['uses' => 'CurrencyController@index', 'as' => 'currency //Route::get('/currency/delete/{currency}', ['uses' => 'CurrencyController@delete', 'as' => 'currency.delete']); //Route::get('/currency/default/{currency}', ['uses' => 'CurrencyController@defaultCurrency', 'as' => 'currency.default']); -/* +/** + * 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/bills', ['uses' => 'GoogleChartController@billsOverview']); +//Route::get('/chart/account/{account}/{view?}', ['uses' => 'GoogleChartController@accountBalanceChart']); +//Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']); +//Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']); +//Route::get('/chart/bills/{bill}', ['uses' => 'GoogleChartController@billOverview']); + + +/** * Piggy Bank Controller */ // piggy bank controller @@ -70,28 +82,27 @@ Route::get('/piggy_banks', ['uses' => 'PiggyBankController@index', 'as' => 'pigg //Route::get('/piggy_banks/delete/{piggyBank}', ['uses' => 'PiggyBankController@delete', 'as' => 'piggy_banks.delete']); //Route::get('/piggy_banks/show/{piggyBank}', ['uses' => 'PiggyBankController@show', 'as' => 'piggy_banks.show']); -/* +/** * 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']); -/* +/** * Repeated Expenses Controller */ -// repeated expenses controller: Route::get('/repeatedexpenses', ['uses' => 'RepeatedExpenseController@index', 'as' => 'repeated.index']); //Route::get('/repeatedexpenses/create', ['uses' => 'RepeatedExpenseController@create', 'as' => 'repeated.create']); //Route::get('/repeatedexpenses/edit/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@edit', 'as' => 'repeated.edit']); //Route::get('/repeatedexpenses/delete/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@delete', 'as' => 'repeated.delete']); //Route::get('/repeatedexpenses/show/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@show', 'as' => 'repeated.show']); -/* +/** * Report Controller */ Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']); @@ -99,15 +110,14 @@ Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.ind //Route::get('/reports/{year}/{month}', ['uses' => 'ReportController@month', 'as' => 'reports.month']); //Route::get('/reports/budget/{year}/{month}', ['uses' => 'ReportController@budget', 'as' => 'reports.budget']); -/* +/** * Search Controller */ Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']); -/* +/** * Transaction Controller */ -// transaction controller: Route::get('/transactions/{what}', ['uses' => 'TransactionController@index', 'as' => 'transactions.index'])->where( ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] ); @@ -123,7 +133,7 @@ Route::get('/transaction/show/{tj}', ['uses' => 'TransactionController@show', 'a //Route::post('/transactions/doRelate', ['uses' => 'TransactionController@doRelate', 'as' => 'transactions.doRelate']); //Route::any('/transactions/unrelate/{tj}', ['uses' => 'TransactionController@unrelate', 'as' => 'transactions.unrelate']); -/* +/** * User Controller * TODO move to AuthController */ diff --git a/app/Models/Account.php b/app/Models/Account.php index ec0b931223..52895ec134 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -26,6 +26,11 @@ class Account extends Model return $this->belongsTo('FireflyIII\User'); } + public function transactions() + { + return $this->hasMany('FireflyIII\Models\Transaction'); + } + public function scopeAccountTypeIn(EloquentBuilder $query, array $types) { if (is_null($this->joinedAccountTypes)) { diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index ada65f6e16..15f6a1e2db 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -1,7 +1,10 @@ belongsToMany('FireflyIII\Models\Category'); } + public function getDates() + { + return ['created_at', 'updated_at', 'date']; + } + public function getDescriptionAttribute($value) { if ($this->encrypted) { @@ -36,6 +44,59 @@ class TransactionJournal extends Model return $this->hasMany('FireflyIII\Models\PiggyBankEvent'); } + /** + * @param EloquentBuilder $query + * @param Carbon $date + * + * @return mixed + */ + public function scopeAfter(EloquentBuilder $query, Carbon $date) + { + return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00')); + } + + /** + * @param EloquentBuilder $query + * @param Carbon $date + * + * @return mixed + */ + public function scopeBefore(EloquentBuilder $query, Carbon $date) + { + return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); + } + + /** + * @param EloquentBuilder $query + * @param $amount + */ + public function scopeLessThan(EloquentBuilder $query, $amount) + { + if (is_null($this->joinedTransactions)) { + $query->leftJoin( + 'transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id' + ); + $this->joinedTransactions = true; + } + + $query->where('transactions.amount', '<=', $amount); + } + + /** + * @param EloquentBuilder $query + * @param array $types + */ + public function scopeTransactionTypes(EloquentBuilder $query, array $types) + { + if (is_null($this->joinedTransactionTypes)) { + $query->leftJoin( + 'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' + ); + $this->joinedTransactionTypes = true; + } + $query->whereIn('transaction_types.type', $types); + } + public function setDescriptionAttribute($value) { $this->attributes['description'] = \Crypt::encrypt($value); @@ -67,10 +128,4 @@ class TransactionJournal extends Model return $this->belongsTo('FireflyIII\User'); } - public function getDates() - { - return ['created_at', 'updated_at','date']; - } - - } diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 3c0b53e882..88d9837a6b 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -23,6 +23,12 @@ class FireflyServiceProvider extends ServiceProvider return new \FireflyIII\Support\Amount; } ); + + $this->app->bind( + 'steam', function () { + return new \FireflyIII\Support\Steam; + } + ); } } \ No newline at end of file diff --git a/app/Support/Amount.php b/app/Support/Amount.php index af02b5b8e8..019f6cc0c1 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -3,6 +3,9 @@ namespace FireflyIII\Support; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use Preferences as Prefs; +use Cache; /** * Class Amount * @@ -54,4 +57,29 @@ class Amount // € return $symbol . ' ' . $string; } + + /** + * @return string + */ + public function getCurrencyCode() + { + if (defined('FFCURRENCYCODE')) { + return FFCURRENCYCODE; + } + if (Cache::has('FFCURRENCYCODE')) { + define('FFCURRENCYCODE', Cache::get('FFCURRENCYCODE')); + + return FFCURRENCYCODE; + } + + + $currencyPreference = Prefs::get('currencyPreference', 'EUR'); + $currency = TransactionCurrency::whereCode($currencyPreference->data)->first(); + + \Cache::forever('FFCURRENCYCODE', $currency->code); + + define('FFCURRENCYCODE', $currency->code); + + return $currency->code; + } } \ No newline at end of file diff --git a/app/Support/Facades/Steam.php b/app/Support/Facades/Steam.php new file mode 100644 index 0000000000..dc8e3fd3eb --- /dev/null +++ b/app/Support/Facades/Steam.php @@ -0,0 +1,23 @@ +transactions()->leftJoin( + 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' + )->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount') + ); + + return $balance; + } + +} \ No newline at end of file diff --git a/codeception.yml b/codeception.yml new file mode 100644 index 0000000000..1c64b071f9 --- /dev/null +++ b/codeception.yml @@ -0,0 +1,22 @@ +actor: Tester +paths: + tests: tests + log: tests/_output + data: tests/_data + helpers: tests/_support +settings: + bootstrap: _bootstrap.php + colors: true + memory_limit: 1024M +modules: + config: +coverage: + enabled: true + remote: false + whitelist: + include: + - app/controllers/* + - app/models/* + - app/lib/FireflyIII/* + exclude: + - app/controllers/BaseController.php diff --git a/config/app.php b/config/app.php index 66080a7d2a..377fc5eedf 100644 --- a/config/app.php +++ b/config/app.php @@ -201,6 +201,7 @@ return [ 'Preferences' => 'FireflyIII\Support\Facades\Preferences', 'Navigation' => 'FireflyIII\Support\Facades\Navigation', 'Amount' => 'FireflyIII\Support\Facades\Amount', + 'Steam' => 'FireflyIII\Support\Facades\Steam', ], diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index 6dceb03a91..3a212eb533 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -114,7 +114,7 @@ @stop @section('scripts')