diff --git a/app/assets/javascripts/categories.js b/app/assets/javascripts/categories.js
new file mode 100644
index 0000000000..f2c72e3b14
--- /dev/null
+++ b/app/assets/javascripts/categories.js
@@ -0,0 +1,14 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
+// gets included (e.g. say you have require_tree . then the code will appear after all the directories
+// but before any files alphabetically greater than 'application.js'
+//
+// The available directives right now are require, require_directory, and require_tree
+//
+//= require_tree highcharts
+//= require firefly/categories
diff --git a/app/assets/javascripts/firefly/categories.js b/app/assets/javascripts/firefly/categories.js
new file mode 100644
index 0000000000..d338be7540
--- /dev/null
+++ b/app/assets/javascripts/firefly/categories.js
@@ -0,0 +1,90 @@
+$(function () {
+if($('#chart').length == 1) {
+ /**
+ * get data from controller for home charts:
+ */
+ $.getJSON('chart/categories/show/' + categoryID).success(function (data) {
+ var options = {
+ chart: {
+ renderTo: 'chart',
+ type: 'column'
+ },
+ series: [data.series],
+ title: {
+ text: data.chart_title
+ },
+ yAxis: {
+ formatter: function () {
+ return '$' + Highcharts.numberFormat(this.y, 0);
+ }
+ },
+ subtitle: {
+ text: data.subtitle,
+ useHTML: true
+ },
+
+ xAxis: {
+ floor: 0,
+ type: 'category',
+ title: {
+ text: 'Period'
+ }
+ },
+ tooltip: {
+ shared: true,
+ crosshairs: false,
+ formatter: function () {
+ var str = '' + Highcharts.dateFormat("%A, %e %B", this.x) + '
';
+ for (x in this.points) {
+ var point = this.points[x];
+ var colour = point.point.pointAttr[''].fill;
+ str += '' + point.series.name + ': € ' + Highcharts.numberFormat(point.y, 2) + '
';
+ }
+ //console.log();
+ return str;
+ return '' + this.series.name + ' on ' + Highcharts.dateFormat("%e %B", this.x) + ':
€ ' + Highcharts.numberFormat(this.y, 2);
+ }
+ },
+ plotOptions: {
+ line: {
+ shadow: true
+ },
+ series: {
+ cursor: 'pointer',
+ negativeColor: '#FF0000',
+ threshold: 0,
+ lineWidth: 1,
+ marker: {
+ radius: 2
+ },
+ point: {
+ events: {
+ click: function (e) {
+ hs.htmlExpand(null, {
+ src: 'chart/home/info/' + this.series.name + '/' + Highcharts.dateFormat("%d/%m/%Y", this.x),
+ pageOrigin: {
+ x: e.pageX,
+ y: e.pageY
+ },
+ objectType: 'ajax',
+ headingText: '' + this.series.name + '',
+ width: 250
+ }
+ )
+ ;
+ }
+ }
+ }
+ }
+ },
+ credits: {
+ enabled: false
+ }
+ };
+ $('#chart').highcharts(options);
+ });
+}
+
+
+
+});
\ No newline at end of file
diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php
index 9e720e9a27..b6ddac3363 100644
--- a/app/controllers/BudgetController.php
+++ b/app/controllers/BudgetController.php
@@ -158,7 +158,6 @@ class BudgetController extends BaseController
return Redirect::route('budgets.index.budget');
}
- return Redirect::route('budgets.index');
}
diff --git a/app/controllers/CategoryController.php b/app/controllers/CategoryController.php
index 787835a4ad..b098fbefca 100644
--- a/app/controllers/CategoryController.php
+++ b/app/controllers/CategoryController.php
@@ -1,6 +1,7 @@
_repository = $repository;
+ $this->_category = $category;
View::share('menu', 'categories');
}
public function create()
{
+ return View::make('categories.create');
}
public function delete(Category $category)
{
- return View::make('categories.delete')->with('category',$category);
+ return View::make('categories.delete')->with('category', $category);
}
public function destroy()
{
+ $result = $this->_repository->destroy(Input::get('id'));
+ if ($result === true) {
+ Session::flash('success', 'The category was deleted.');
+ } else {
+ Session::flash('error', 'Could not delete the category. Check the logs to be sure.');
+ }
+ return Redirect::route('categories.index');
}
public function edit(Category $category)
{
- return View::make('categories.edit')->with('category',$category);
+ return View::make('categories.edit')->with('category', $category);
}
public function index()
{
$categories = $this->_repository->get();
- return View::make('categories.index')->with('categories',$categories);
+ return View::make('categories.index')->with('categories', $categories);
}
public function show(Category $category)
{
- return View::make('categories.show')->with('category',$category);
+ $start = \Session::get('start');
+ $end = \Session::get('end');
+
+
+ $journals = $this->_category->journalsInRange($category, $start, $end);
+
+ return View::make('categories.show')->with('category', $category)->with('journals',$journals);
}
public function store()
{
+ $category = $this->_repository->store(Input::all());
+ if ($category->id) {
+ Session::flash('success', 'Category created!');
+
+ if (Input::get('create') == '1') {
+ return Redirect::route('categories.create');
+ }
+ return Redirect::route('categories.index');
+ } else {
+ Session::flash('error', 'Could not save the new category!');
+ return Redirect::route('categories.create')->withInput();
+ }
}
public function update()
{
+ $category = $this->_repository->update(Input::all());
+ Session::flash('success', 'Category "' . $category->name . '" updated.');
+
+ return Redirect::route('categories.index');
}
diff --git a/app/controllers/ChartController.php b/app/controllers/ChartController.php
index 20abf95400..6e8388241c 100644
--- a/app/controllers/ChartController.php
+++ b/app/controllers/ChartController.php
@@ -110,5 +110,21 @@ class ChartController extends BaseController
return Response::json($this->_chart->categories($start, $end));
+ }
+ public function categoryShowChart(Category $category) {
+ $start = Session::get('start');
+ $end = Session::get('end');
+ $range = Session::get('range');
+
+ $serie = $this->_chart->categoryShowChart($category, $range, $start, $end);
+ $data = [
+ 'chart_title' => $category->name,
+ 'subtitle' => 'View more',
+ 'series' => $serie
+ ];
+ return Response::json($data);
+
+
+
}
}
\ No newline at end of file
diff --git a/app/lib/Firefly/Helper/Controllers/Category.php b/app/lib/Firefly/Helper/Controllers/Category.php
new file mode 100644
index 0000000000..1f31dd9c04
--- /dev/null
+++ b/app/lib/Firefly/Helper/Controllers/Category.php
@@ -0,0 +1,24 @@
+transactionjournals()->
+ with(['transactions','transactions.account','transactiontype','components'])->
+
+ orderBy('date','DESC')->orderBy('id','DESC')->before($end)->after($start)->get();
+
+ }
+}
\ No newline at end of file
diff --git a/app/lib/Firefly/Helper/Controllers/CategoryInterface.php b/app/lib/Firefly/Helper/Controllers/CategoryInterface.php
new file mode 100644
index 0000000000..621c6a6600
--- /dev/null
+++ b/app/lib/Firefly/Helper/Controllers/CategoryInterface.php
@@ -0,0 +1,18 @@
+ [],
- 'sum' => 0
+ 'sum' => 0
];
if ($account) {
// get journals in range:
@@ -85,7 +85,7 @@ class Chart implements ChartInterface
$data = [];
$budgets = \Auth::user()->budgets()->with(
- ['limits' => function ($q) {
+ ['limits' => function ($q) {
$q->orderBy('limits.startdate', 'ASC');
}, 'limits.limitrepetitions' => function ($q) use ($start) {
$q->orderBy('limit_repetitions.startdate', 'ASC');
@@ -154,6 +154,136 @@ class Chart implements ChartInterface
return $data;
}
+ public function categoryShowChart(\Category $category, $range, Carbon $start, Carbon $end)
+ {
+ $data = ['name' => $category->name . ' per ' . $range, 'data' => []];
+ // go back twelve periods. Skip if empty.
+ $beginning = clone $start;
+ switch ($range) {
+ default:
+ throw new FireflyException('No beginning for range ' . $range);
+ break;
+ case '1D':
+ $beginning->subDays(12);
+ break;
+ case '1W':
+ $beginning->subWeeks(12);
+ break;
+ case '1M':
+ $beginning->subYear();
+ break;
+ case '3M':
+ $beginning->subYears(3);
+ break;
+ case '6M':
+ $beginning->subYears(6);
+ break;
+ }
+ // loop over the periods:
+ while ($beginning <= $start) {
+ // increment currentEnd to fit beginning:
+ $currentEnd = clone $beginning;
+ // increase beginning for next round:
+ switch ($range) {
+ default:
+ throw new FireflyException('No currentEnd incremental for range ' . $range);
+ break;
+ case '1D':
+ break;
+ case '1W':
+ $currentEnd->addWeek()->subDay();
+ break;
+ case '1M':
+ $currentEnd->addMonth()->subDay();
+ break;
+ case '3M':
+ $currentEnd->addMonths(3)->subDay();
+ break;
+ case '6M':
+ $currentEnd->addMonths(6)->subDay();
+
+ }
+
+ // now format the current range:
+ $title = '';
+ switch ($range) {
+ default:
+ throw new \Firefly\Exception\FireflyException('No date formats for frequency "' . $range . '"!');
+ break;
+ case '1D':
+ $title = $beginning->format('j F Y');
+ break;
+ case '1W':
+ $title = $beginning->format('\W\e\e\k W, Y');
+ break;
+ case '1M':
+ $title = $beginning->format('F Y');
+ break;
+ case '3M':
+ case '6M':
+ $title = $beginning->format('M Y') . ' - ' . $currentEnd->format('M Y');
+ break;
+ case 'yearly':
+// return $this->startdate->format('Y');
+ break;
+ }
+
+ // get sum for current range:
+ $journals = \TransactionJournal::
+ with(
+ ['transactions' => function ($q) {
+ $q->where('amount', '>', 0);
+ }]
+ )
+ ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->where('transaction_types.type', 'Withdrawal')
+ ->leftJoin('component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin('components', 'components.id', '=', 'component_transaction_journal.component_id')
+ ->where('components.id', '=', $category->id)
+ //->leftJoin()
+ ->after($beginning)->before($currentEnd)
+ ->where('completed', 1)
+ ->get(['transaction_journals.*']);
+ $currentSum = 0;
+ foreach ($journals as $journal) {
+ if (!isset($journal->transactions[0])) {
+ throw new FireflyException('Journal #' . $journal->id . ' has ' . count($journal->transactions)
+ . ' transactions!');
+ }
+ $transaction = $journal->transactions[0];
+ $amount = floatval($transaction->amount);
+ $currentSum += $amount;
+
+ }
+ $data['data'][] = [$title, $currentSum];
+
+ // increase beginning for next round:
+ switch ($range) {
+ default:
+ throw new FireflyException('No incremental for range ' . $range);
+ break;
+ case '1D':
+ $beginning->addDay();
+ break;
+ case '1W':
+ $beginning->addWeek();
+ break;
+ case '1M':
+ $beginning->addMonth();
+ break;
+ case '3M':
+ $beginning->addMonths(3);
+ break;
+ case '6M':
+ $beginning->addMonths(6);
+ break;
+ }
+ }
+ return $data;
+
+
+ }
+
public function categories(Carbon $start, Carbon $end)
{
@@ -192,8 +322,7 @@ class Chart implements ChartInterface
// sort
arsort($result);
- $chartData = [
- ];
+ $chartData = [];
foreach ($result as $name => $value) {
$chartData[] = [$name, $value];
}
@@ -202,15 +331,4 @@ class Chart implements ChartInterface
return $chartData;
}
- public function accountXX(\Account $account)
- {
- $data = [
- 'chart_title' => $account->name,
- 'subtitle' => 'View more',
- 'series' => [$this->_account($account)]
- ];
-
- return $data;
- }
-
}
\ No newline at end of file
diff --git a/app/lib/Firefly/Helper/Controllers/ChartInterface.php b/app/lib/Firefly/Helper/Controllers/ChartInterface.php
index 3839b546f9..c5e9f9e1bb 100644
--- a/app/lib/Firefly/Helper/Controllers/ChartInterface.php
+++ b/app/lib/Firefly/Helper/Controllers/ChartInterface.php
@@ -21,4 +21,6 @@ interface ChartInterface
public function budgets(Carbon $start);
public function accountDailySummary(\Account $account, Carbon $date);
+
+ public function categoryShowChart(\Category $category, $range, Carbon $start, Carbon $end);
}
\ No newline at end of file
diff --git a/app/lib/Firefly/Helper/HelperServiceProvider.php b/app/lib/Firefly/Helper/HelperServiceProvider.php
index c60f39553a..2b812009c4 100644
--- a/app/lib/Firefly/Helper/HelperServiceProvider.php
+++ b/app/lib/Firefly/Helper/HelperServiceProvider.php
@@ -26,6 +26,10 @@ class HelperServiceProvider extends ServiceProvider
'Firefly\Helper\Controllers\ChartInterface',
'Firefly\Helper\Controllers\Chart'
);
+ $this->app->bind(
+ 'Firefly\Helper\Controllers\CategoryInterface',
+ 'Firefly\Helper\Controllers\Category'
+ );
$this->app->bind(
'Firefly\Helper\Controllers\BudgetInterface',
diff --git a/app/lib/Firefly/Storage/Budget/BudgetRepositoryInterface.php b/app/lib/Firefly/Storage/Budget/BudgetRepositoryInterface.php
index 9decd1ca5b..88debbad6b 100644
--- a/app/lib/Firefly/Storage/Budget/BudgetRepositoryInterface.php
+++ b/app/lib/Firefly/Storage/Budget/BudgetRepositoryInterface.php
@@ -40,7 +40,7 @@ interface BudgetRepositoryInterface
*
* @return mixed
*/
- public function destroy($data);
+ public function destroy($budgetId);
/**
* @param $budgetId
diff --git a/app/lib/Firefly/Storage/Category/CategoryRepositoryInterface.php b/app/lib/Firefly/Storage/Category/CategoryRepositoryInterface.php
index efffd51d64..f6ab03d581 100644
--- a/app/lib/Firefly/Storage/Category/CategoryRepositoryInterface.php
+++ b/app/lib/Firefly/Storage/Category/CategoryRepositoryInterface.php
@@ -14,6 +14,7 @@ interface CategoryRepositoryInterface
* @return mixed
*/
public function get();
+ public function find($categoryId);
/**
* @param $name
@@ -30,10 +31,19 @@ interface CategoryRepositoryInterface
public function findByName($name);
/**
- * @param $name
+ * @param $data
*
* @return mixed
*/
- public function store($name);
+ public function store($data);
+
+ public function update($data);
+
+ /**
+ * @param $data
+ *
+ * @return mixed
+ */
+ public function destroy($categoryId);
}
\ No newline at end of file
diff --git a/app/lib/Firefly/Storage/Category/EloquentCategoryRepository.php b/app/lib/Firefly/Storage/Category/EloquentCategoryRepository.php
index d3b727edff..c703200dbd 100644
--- a/app/lib/Firefly/Storage/Category/EloquentCategoryRepository.php
+++ b/app/lib/Firefly/Storage/Category/EloquentCategoryRepository.php
@@ -14,7 +14,12 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*/
public function get()
{
- return \Auth::user()->categories()->orderBy('name','ASC')->get();
+ return \Auth::user()->categories()->orderBy('name', 'ASC')->get();
+ }
+
+ public function find($categoryId)
+ {
+ return \Auth::user()->categories()->find($categoryId);
}
/**
@@ -26,7 +31,7 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
{
$category = $this->findByName($name);
if (!$category) {
- return $this->store($name);
+ return $this->store(['name' => $name]);
}
return $category;
@@ -54,14 +59,39 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*
* @return \Category|mixed
*/
- public function store($name)
+ public function store($data)
{
- $category = new \Category();
- $category->name = $name;
+ $category = new \Category;
+ $category->name = $data['name'];
+
$category->user()->associate(\Auth::user());
$category->save();
+ return $category;
+ }
+
+ public function update($data)
+ {
+ $category = $this->find($data['id']);
+ if ($category) {
+ // update account accordingly:
+ $category->name = $data['name'];
+ if ($category->validate()) {
+ $category->save();
+ }
+ }
return $category;
}
+ public function destroy($categoryId)
+ {
+ $category = $this->find($categoryId);
+ if ($category) {
+ $category->delete();
+
+ return true;
+ }
+
+ return false;
+ }
}
\ No newline at end of file
diff --git a/app/lib/Firefly/Storage/StorageServiceProvider.php b/app/lib/Firefly/Storage/StorageServiceProvider.php
index 9d032a4e52..2fa2f0f518 100644
--- a/app/lib/Firefly/Storage/StorageServiceProvider.php
+++ b/app/lib/Firefly/Storage/StorageServiceProvider.php
@@ -27,6 +27,8 @@ class StorageServiceProvider extends ServiceProvider
);
+
+
$this->app->bind(
'Firefly\Storage\Account\AccountRepositoryInterface',
'Firefly\Storage\Account\EloquentAccountRepository'
diff --git a/app/models/Category.php b/app/models/Category.php
index 545f2b9ad8..067f795bc0 100644
--- a/app/models/Category.php
+++ b/app/models/Category.php
@@ -29,4 +29,9 @@ class Category extends Component
'class' => 'Category'
];
protected $isSubclass = true;
+
+ public function transactionjournals()
+ {
+ return $this->belongsToMany('TransactionJournal', 'component_transaction_journal', 'component_id');
+ }
}
\ No newline at end of file
diff --git a/app/routes.php b/app/routes.php
index 680485e96f..edadc1de26 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -20,6 +20,15 @@ Route::bind('budget', function($value, $route)
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;
+});
// protected routes:
@@ -34,6 +43,7 @@ Route::group(['before' => 'auth'], function () {
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/info/{accountname}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']);
+ Route::get('/chart/categories/show/{category}', ['uses' => 'ChartController@categoryShowChart','as' => 'chart.showcategory']);
// Categories controller:
Route::get('/categories',['uses' => 'CategoryController@index','as' => 'categories.index']);
@@ -100,6 +110,11 @@ Route::group(['before' => 'csrf|auth'], function () {
Route::post('/budgets/update', ['uses' => 'BudgetController@update', 'as' => 'budgets.update']);
Route::post('/budgets/destroy', ['uses' => 'BudgetController@destroy', 'as' => 'budgets.destroy']);
+ // category controller
+ Route::post('/categories/store',['uses' => 'CategoryController@store', 'as' => 'categories.store']);
+ Route::post('/categories/update', ['uses' => 'CategoryController@update', 'as' => 'categories.update']);
+ Route::post('/categories/destroy', ['uses' => 'CategoryController@destroy', 'as' => 'categories.destroy']);
+
// migration controller
Route::post('/migrate', ['uses' => 'MigrationController@postIndex']);
diff --git a/app/views/categories/create.blade.php b/app/views/categories/create.blade.php
new file mode 100644
index 0000000000..464445d4db
--- /dev/null
+++ b/app/views/categories/create.blade.php
@@ -0,0 +1,64 @@
+@extends('layouts.default')
+@section('content')
+
Use categories to group your expenses
++ Use categories to group expenses by hobby, for certain types of groceries or what bills are for. + Expenses grouped in categories do not have to reoccur every month or every week, like budgets. +
+{{$errors->first('name')}}
+ @else + For example: bike, utilities, daily groceries + @endif ++ Remember that deleting something is permanent. +
++ + Account "{{{$category->name}}}" still has {{$category->transactionjournals()->count()}} transaction(s) associated to it. + These will NOT be deleted but will lose their connection to the category. +
+ @endif + ++ Press "Delete permanently" If you are sure you want to delete "{{{$category->name}}}". +
+Use categories to group your expenses
+{{$errors->first('name')}}
+ @else + For example: bike, utilities, daily groceries + @endif +Use categories to group your expenses
++ Use categories to group expenses by hobby, for certain types of groceries or what bills are for. + Expenses grouped in categories do not have to reoccur every month or every week, like budgets. +
diff --git a/app/views/categories/show.blade.php b/app/views/categories/show.blade.php new file mode 100644 index 0000000000..a92de10f34 --- /dev/null +++ b/app/views/categories/show.blade.php @@ -0,0 +1,47 @@ +@extends('layouts.default') +@section('content') + + + +Use categories to group your expenses
++ Use categories to group expenses by hobby, for certain types of groceries or what bills are for. + Expenses grouped in categories do not have to reoccur every month or every week, like budgets. +
++ This overview will show you the expenses you've made in each [period] and show you the actual + transactions for the currently selected period. +
+(Some chart here)