Continued working on category controller [skip ci]

This commit is contained in:
James Cole 2014-07-30 14:45:46 +02:00
parent 04a9ada682
commit 00a767cfc9
22 changed files with 619 additions and 36 deletions

View File

@ -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

View File

@ -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 = '<span style="font-size:80%;">' + Highcharts.dateFormat("%A, %e %B", this.x) + '</span><br />';
for (x in this.points) {
var point = this.points[x];
var colour = point.point.pointAttr[''].fill;
str += '<span style="color:' + colour + '">' + point.series.name + '</span>: € ' + Highcharts.numberFormat(point.y, 2) + '<br />';
}
//console.log();
return str;
return '<span style="font-size:80%;">' + this.series.name + ' on ' + Highcharts.dateFormat("%e %B", this.x) + ':</span><br /> € ' + 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: '<a href="#">' + this.series.name + '</a>',
width: 250
}
)
;
}
}
}
}
},
credits: {
enabled: false
}
};
$('#chart').highcharts(options);
});
}
});

View File

@ -158,7 +158,6 @@ class BudgetController extends BaseController
return Redirect::route('budgets.index.budget');
}
return Redirect::route('budgets.index');
}

View File

@ -1,6 +1,7 @@
<?php
use Firefly\Storage\Category\CategoryRepositoryInterface as CRI;
use Firefly\Helper\Controllers\CategoryInterface as CI;
/**
* Class CategoryController
@ -9,47 +10,78 @@ class CategoryController extends BaseController
{
protected $_repository;
public function __construct(CRI $repository)
public function __construct(CRI $repository, CI $category)
{
$this->_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');
}

View File

@ -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' => '<a href="' . route('categories.show', [$category->id]) . '">View more</a>',
'series' => $serie
];
return Response::json($data);
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Created by PhpStorm.
* User: User
* Date: 30-7-14
* Time: 10:57
*/
namespace Firefly\Helper\Controllers;
use Carbon\Carbon;
class Category implements CategoryInterface
{
public function journalsInRange(\Category $category, Carbon $start, Carbon $end)
{
return $category->transactionjournals()->
with(['transactions','transactions.account','transactiontype','components'])->
orderBy('date','DESC')->orderBy('id','DESC')->before($end)->after($start)->get();
}
}

View File

@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: User
* Date: 30-7-14
* Time: 10:57
*/
namespace Firefly\Helper\Controllers;
use Carbon\Carbon;
interface CategoryInterface {
public function journalsInRange(\Category $category, Carbon $start, Carbon $end);
}

View File

@ -36,7 +36,7 @@ class Chart implements ChartInterface
{
$result = [
'rows' => [],
'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' => '<a href="' . route('accounts.show', [$account->id]) . '">View more</a>',
'series' => [$this->_account($account)]
];
return $data;
}
}

View File

@ -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);
}

View File

@ -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',

View File

@ -40,7 +40,7 @@ interface BudgetRepositoryInterface
*
* @return mixed
*/
public function destroy($data);
public function destroy($budgetId);
/**
* @param $budgetId

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -27,6 +27,8 @@ class StorageServiceProvider extends ServiceProvider
);
$this->app->bind(
'Firefly\Storage\Account\AccountRepositoryInterface',
'Firefly\Storage\Account\EloquentAccountRepository'

View File

@ -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');
}
}

View File

@ -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']);

View File

@ -0,0 +1,64 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Create a category</small>
</h1>
<p class="lead">Use categories to group your expenses</p>
<p class="text-info">
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.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('categories.store')])}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<h4>Mandatory fields</h4>
<div class="form-group">
<label for="name" class="col-sm-4 control-label">Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" id="name" value="{{Input::old('name')}}" placeholder="Name">
@if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<span class="help-block">For example: bike, utilities, daily groceries</span>
@endif
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<!-- add another after this one? -->
<div class="form-group">
<label for="create" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<div class="checkbox">
<label>
{{Form::checkbox('create',1,Input::old('create') == '1')}}
Create another (return to this form)
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<button type="submit" class="btn btn-default btn-success">Create the category</button>
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -0,0 +1,46 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Delete "{{{$category->name}}}"</small>
</h1>
<p class="lead">
Remember that deleting something is permanent.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('categories.destroy')])}}
{{Form::hidden('id',$category->id)}}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
@if($category->transactionjournals()->count() > 0)
<p class="text-info">
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.
</p>
@endif
<p class="text-danger">
Press "Delete permanently" If you are sure you want to delete "{{{$category->name}}}".
</p>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<div class="col-sm-8">
<button type="submit" class="btn btn-default btn-danger">Delete permanently</button>
<a href="{{route('categories.index')}}" class="btn-default btn">Cancel</a>
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -0,0 +1,50 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Edit category "{{{$category->name}}}"</small>
</h1>
<p class="lead">Use categories to group your expenses</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('categories.update')])}}
{{Form::hidden('id',$category->id)}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<h4>Mandatory fields</h4>
<div class="form-group">
<label for="name" class="col-sm-4 control-label">Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" id="name" value="{{Input::old('name') ?: $category->name}}" placeholder="Name">
@if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<span class="help-block">For example: bike, utilities, daily groceries</span>
@endif
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<button type="submit" class="btn btn-default btn-success">Update the category</button>
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -6,6 +6,10 @@
<small>Categories</small>
</h1>
<p class="lead">Use categories to group your expenses</p>
<p class="text-info">
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.
</p>
</div>
</div>

View File

@ -0,0 +1,47 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Category "{{{$category->name}}}"</small>
</h1>
<p class="lead">Use categories to group your expenses</p>
<p class="text-info">
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.
</p>
<p class="text-info">
This overview will show you the expenses you've made in each [period] and show you the actual
transactions for the currently selected period.
</p>
</div>
</div>
@include('partials.date_nav')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div id="chart"><p class="small text-center">(Some chart here)</p></div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h4>Transactions<small> in current range</small></h4>
@include('lists.transactions',['journals' => $journals,'sum' => true])
</div>
</div>
@stop
@section('scripts')
<script type="text/javascript">
var categoryID = {{$category->id}};
</script>
<?php echo javascript_include_tag('categories'); ?>
@stop

View File

@ -32,13 +32,6 @@
@include('partials.flashes')
@yield('content')
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<!--
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="assets/javascript/bootstrap3-typeahead.min.js"></script>
-->
<?php echo javascript_include_tag(); ?>
@yield('scripts')
</body>