mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-05 12:12:18 +00:00
New stuff! [skip ci]
This commit is contained in:
16
app/assets/javascripts/accounts.js
Normal file
16
app/assets/javascripts/accounts.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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 highslide/highslide-full.min
|
||||
//= require highslide/highslide.config
|
||||
//= require_tree highcharts
|
||||
//= require firefly/accounts
|
95
app/assets/javascripts/firefly/accounts.js
Normal file
95
app/assets/javascripts/firefly/accounts.js
Normal file
@@ -0,0 +1,95 @@
|
||||
$(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: 'line'
|
||||
},
|
||||
|
||||
series: data,
|
||||
title: {
|
||||
text: 'BETTER TITLE HERE'
|
||||
},
|
||||
yAxis: {
|
||||
formatter: function () {
|
||||
return '$' + Highcharts.numberFormat(this.y, 0);
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: '<a href="#">View more</a>',
|
||||
useHTML: true
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
floor: 0,
|
||||
type: 'datetime',
|
||||
dateTimeLabelFormats: {
|
||||
day: '%e %b',
|
||||
year: '%b'
|
||||
},
|
||||
title: {
|
||||
text: 'Date'
|
||||
}
|
||||
},
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
13
app/assets/stylesheets/accounts.css
Normal file
13
app/assets/stylesheets/accounts.css
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any Css/Less files 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.css'
|
||||
*
|
||||
*= require highslide/highslide
|
||||
*/
|
@@ -51,13 +51,11 @@ class AccountController extends \BaseController
|
||||
$result = $this->_repository->destroy(Input::get('id'));
|
||||
if ($result === true) {
|
||||
Session::flash('success', 'The account was deleted.');
|
||||
|
||||
return Redirect::route('accounts.index');
|
||||
} else {
|
||||
Session::flash('danger', 'Could not delete the account. Check the logs to be sure.');
|
||||
|
||||
return Redirect::route('accounts.index');
|
||||
Session::flash('error', 'Could not delete the account. Check the logs to be sure.');
|
||||
}
|
||||
return Redirect::route('accounts.index');
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -93,7 +91,9 @@ class AccountController extends \BaseController
|
||||
*/
|
||||
public function show(Account $account)
|
||||
{
|
||||
return View::make('accounts.show')->with('account', $account);
|
||||
$show = $this->_accounts->show($account, 40);
|
||||
|
||||
return View::make('accounts.show')->with('account', $account)->with('show',$show);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Firefly\Helper\Controllers\BudgetInterface as BI;
|
||||
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
|
||||
|
||||
use Carbon\Carbon;
|
||||
/**
|
||||
* Class BudgetController
|
||||
*/
|
||||
@@ -9,16 +10,40 @@ class BudgetController extends BaseController
|
||||
{
|
||||
|
||||
protected $_budgets;
|
||||
protected $_repository;
|
||||
|
||||
/**
|
||||
* @param BRI $budgets
|
||||
*/
|
||||
public function __construct(BRI $budgets)
|
||||
public function __construct(BI $budgets, BRI $repository)
|
||||
{
|
||||
$this->_budgets = $budgets;
|
||||
$this->_repository = $repository;
|
||||
View::share('menu', 'budgets');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
|
||||
return View::make('budgets.create')->with('periods', $periods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
*/
|
||||
public function indexByBudget()
|
||||
{
|
||||
$budgets = $this->_repository->get();
|
||||
$today = new Carbon;
|
||||
|
||||
return View::make('budgets.indexByBudget')->with('budgets', $budgets)->with('today', $today);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
* @throws Firefly\Exception\FireflyException
|
||||
@@ -26,76 +51,12 @@ class BudgetController extends BaseController
|
||||
public function indexByDate()
|
||||
{
|
||||
// get a list of dates by getting all repetitions:
|
||||
$budgets = $this->_budgets->get();
|
||||
$reps = [];
|
||||
foreach ($budgets as $budget) {
|
||||
foreach ($budget->limits as $limit) {
|
||||
$dateFormats = \Config::get('firefly.date_formats_by_period.' . $limit->repeat_freq);
|
||||
if (is_null($dateFormats)) {
|
||||
throw new \Firefly\Exception\FireflyException('No date formats for ' . $limit->repeat_freq);
|
||||
}
|
||||
$set = $this->_repository->get();
|
||||
$budgets = $this->_budgets->organizeByDate($set);
|
||||
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$periodOrder = $rep->startdate->format($dateFormats['group_date']);
|
||||
$period = $rep->startdate->format($dateFormats['display_date']);
|
||||
$reps[$periodOrder] = isset($reps[$periodOrder]) ? $reps[$periodOrder] : ['date' => $period];
|
||||
return View::make('budgets.indexByDate')->with('budgets', $budgets);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// put all the budgets under their respective date:
|
||||
foreach ($budgets as $budget) {
|
||||
foreach ($budget->limits as $limit) {
|
||||
$dateFormats = \Config::get('firefly.date_formats_by_period.' . $limit->repeat_freq);
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
|
||||
$month = $rep->startdate->format($dateFormats['group_date']);
|
||||
$reps[$month]['limitrepetitions'][] = $rep;
|
||||
}
|
||||
}
|
||||
}
|
||||
krsort($reps);
|
||||
|
||||
return View::make('budgets.indexByDate')->with('reps', $reps);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
*/
|
||||
public function indexByBudget()
|
||||
{
|
||||
$budgets = $this->_budgets->get();
|
||||
$today = new \Carbon\Carbon;
|
||||
return View::make('budgets.indexByBudget')->with('budgets', $budgets)->with('today', $today);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
return View::make('budgets.create')->with('periods', $periods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => Input::get('name'),
|
||||
'amount' => floatval(Input::get('amount')),
|
||||
'repeat_freq' => Input::get('period'),
|
||||
'repeats' => intval(Input::get('repeats'))
|
||||
];
|
||||
|
||||
$this->_budgets->store($data);
|
||||
Session::flash('success', 'Budget created!');
|
||||
return Redirect::route('budgets.index');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,5 +107,24 @@ class BudgetController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => Input::get('name'),
|
||||
'amount' => floatval(Input::get('amount')),
|
||||
'repeat_freq' => Input::get('period'),
|
||||
'repeats' => intval(Input::get('repeats'))
|
||||
];
|
||||
|
||||
$this->_budgets->store($data);
|
||||
Session::flash('success', 'Budget created!');
|
||||
|
||||
return Redirect::route('budgets.index');
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -41,6 +41,7 @@ class Account implements AccountInterface
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
|
||||
}
|
||||
@@ -53,6 +54,7 @@ class Account implements AccountInterface
|
||||
public function openingBalanceTransaction(\Account $account)
|
||||
{
|
||||
$transactionType = \TransactionType::where('type', 'Opening balance')->first();
|
||||
|
||||
return \TransactionJournal::
|
||||
with(
|
||||
['transactions' => function ($q) {
|
||||
@@ -63,4 +65,118 @@ class Account implements AccountInterface
|
||||
->where('transactions.account_id', $account->id)->first(['transaction_journals.*']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Account $account
|
||||
* @param $perPage
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function show(\Account $account, $perPage)
|
||||
{
|
||||
$start = \Session::get('start');
|
||||
$end = \Session::get('end');
|
||||
$stats = [
|
||||
'budgets' => [],
|
||||
'categories' => [],
|
||||
'accounts' => []
|
||||
];
|
||||
$items = [];
|
||||
|
||||
|
||||
// build a query:
|
||||
$query = \TransactionJournal::with(
|
||||
['transactions' => function ($q) {
|
||||
$q->orderBy('amount', 'ASC');
|
||||
}, 'transactiontype', 'components' => function ($q) {
|
||||
$q->orderBy('class');
|
||||
}, 'transactions.account.accounttype']
|
||||
)->orderBy('date', 'DESC')->leftJoin(
|
||||
'transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id'
|
||||
)->where('transactions.account_id', $account->id)->where('date', '>=', $start->format('Y-m-d'))->where(
|
||||
'date', '<=', $end->format('Y-m-d')
|
||||
)->orderBy('transaction_journals.id', 'DESC');
|
||||
|
||||
|
||||
// build paginator:
|
||||
$totalItems = $query->count();
|
||||
$page = intval(\Input::get('page')) > 1 ? intval(\Input::get('page')) : 1;
|
||||
$skip = ($page - 1) * $perPage;
|
||||
$result = $query->skip($skip)->take($perPage)->get(['transaction_journals.*']);
|
||||
// in the mean time, build list of categories, budgets and other accounts:
|
||||
|
||||
/** @var $item \TransactionJournal */
|
||||
foreach ($result as $item) {
|
||||
$items[] = $item;
|
||||
foreach ($item->components as $component) {
|
||||
if ($component->class == 'Budget') {
|
||||
$stats['budgets'][$component->id] = $component;
|
||||
}
|
||||
if ($component->class == 'Category') {
|
||||
$stats['categories'][$component->id] = $component;
|
||||
}
|
||||
}
|
||||
$fromAccount = $item->transactions[0]->account;
|
||||
$toAccount = $item->transactions[1]->account;
|
||||
$stats['accounts'][$fromAccount->id] = $fromAccount;
|
||||
$stats['accounts'][$toAccount->id] = $toAccount;
|
||||
}
|
||||
unset($result, $page);
|
||||
$paginator = \Paginator::make($items, $totalItems, $perPage);
|
||||
|
||||
// statistics
|
||||
$stats['period']['in'] = floatval(
|
||||
\Transaction::where('account_id', $account->id)->where('amount', '>', 0)->leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
|
||||
)->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->whereIn('transaction_types.type', ['Deposit', 'Withdrawal'])->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount')
|
||||
);
|
||||
|
||||
|
||||
$stats['period']['out'] = floatval(
|
||||
\Transaction::where('account_id', $account->id)->where('amount', '<', 0)->leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
|
||||
)->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->whereIn('transaction_types.type', ['Deposit', 'Withdrawal'])->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount')
|
||||
);
|
||||
$stats['period']['diff'] = $stats['period']['in'] + $stats['period']['out'];
|
||||
|
||||
$stats['period']['t_in'] = floatval(
|
||||
\Transaction::where('account_id', $account->id)->where('amount', '>', 0)->leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
|
||||
)->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->where('transaction_types.type', 'Transfer')->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount')
|
||||
);
|
||||
|
||||
$stats['period']['t_out'] = floatval(
|
||||
\Transaction::where('account_id', $account->id)->where('amount', '<', 0)->leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
|
||||
)->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->where('transaction_types.type', 'Transfer')->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount')
|
||||
);
|
||||
|
||||
$stats['period']['t_diff'] = $stats['period']['t_in'] + $stats['period']['t_out'];
|
||||
|
||||
|
||||
$return = [
|
||||
'journals' => $paginator,
|
||||
'statistics' => $stats
|
||||
];
|
||||
|
||||
return $return;
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -28,4 +28,12 @@ interface AccountInterface
|
||||
*/
|
||||
public function openingBalanceTransaction(\Account $account);
|
||||
|
||||
/**
|
||||
* @param \Account $account
|
||||
* @param $perPage
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function show(\Account $account, $perPage);
|
||||
|
||||
}
|
61
app/lib/Firefly/Helper/Controllers/Budget.php
Normal file
61
app/lib/Firefly/Helper/Controllers/Budget.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: sander
|
||||
* Date: 27/07/14
|
||||
* Time: 16:28
|
||||
*/
|
||||
|
||||
namespace Firefly\Helper\Controllers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* Class Budget
|
||||
*
|
||||
* @package Firefly\Helper\Controllers
|
||||
*/
|
||||
class Budget implements BudgetInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function organizeByDate(Collection $budgets)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
foreach ($budgets as $budget) {
|
||||
foreach ($budget->limits as $limit) {
|
||||
$dateFormats = \Config::get('firefly.date_formats_by_period.' . $limit->repeat_freq);
|
||||
if (is_null($dateFormats)) {
|
||||
throw new \Firefly\Exception\FireflyException('No date formats for ' . $limit->repeat_freq);
|
||||
}
|
||||
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$periodOrder = $rep->startdate->format($dateFormats['group_date']);
|
||||
$period = $rep->startdate->format($dateFormats['display_date']);
|
||||
$return[$periodOrder] = isset($return[$periodOrder]) ? $return[$periodOrder] : ['date' => $period];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// put all the budgets under their respective date:
|
||||
foreach ($budgets as $budget) {
|
||||
foreach ($budget->limits as $limit) {
|
||||
$dateFormats = \Config::get('firefly.date_formats_by_period.' . $limit->repeat_freq);
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$rep->left = $rep->left();
|
||||
|
||||
$month = $rep->startdate->format($dateFormats['group_date']);
|
||||
$return[$month]['limitrepetitions'][] = $rep;
|
||||
}
|
||||
}
|
||||
}
|
||||
krsort($return);
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
18
app/lib/Firefly/Helper/Controllers/BudgetInterface.php
Normal file
18
app/lib/Firefly/Helper/Controllers/BudgetInterface.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Firefly\Helper\Controllers;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
/**
|
||||
* Interface BudgetInterface
|
||||
*
|
||||
* @package Firefly\Helper\Controllers
|
||||
*/
|
||||
interface BudgetInterface {
|
||||
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function organizeByDate(Collection $budgets);
|
||||
|
||||
}
|
@@ -23,6 +23,11 @@ class HelperServiceProvider extends ServiceProvider
|
||||
'Firefly\Helper\Controllers\Account'
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
'Firefly\Helper\Controllers\BudgetInterface',
|
||||
'Firefly\Helper\Controllers\Budget'
|
||||
);
|
||||
|
||||
// mail:
|
||||
$this->app->bind(
|
||||
'Firefly\Helper\Email\EmailHelperInterface',
|
||||
|
@@ -12,6 +12,40 @@ use Carbon\Carbon;
|
||||
class EloquentBudgetRepository implements BudgetRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param $budgetId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($budgetId)
|
||||
{
|
||||
|
||||
return \Auth::user()->budgets()->find($budgetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$set = \Auth::user()->budgets()->with(
|
||||
['limits' => function ($q) {
|
||||
$q->orderBy('limits.startdate', 'ASC');
|
||||
}, 'limits.limitrepetitions' => function ($q) {
|
||||
$q->orderBy('limit_repetitions.startdate', 'ASC');
|
||||
}]
|
||||
)->orderBy('name', 'ASC')->get();
|
||||
foreach ($set as $budget) {
|
||||
foreach ($budget->limits as $limit) {
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$rep->left = $rep->left();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed
|
||||
*/
|
||||
@@ -24,6 +58,7 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
|
||||
foreach ($list as $entry) {
|
||||
$return[intval($entry->id)] = $entry->name;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -69,6 +104,7 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
|
||||
$budget->count += count($limit->limitrepetitions);
|
||||
}
|
||||
}
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
@@ -123,30 +159,4 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
|
||||
return $budget;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return \Auth::user()->budgets()->with(
|
||||
['limits' => function ($q) {
|
||||
$q->orderBy('limits.startdate', 'ASC');
|
||||
}, 'limits.limitrepetitions' => function ($q) {
|
||||
$q->orderBy('limit_repetitions.startdate', 'ASC');
|
||||
}]
|
||||
)->orderBy('name', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $budgetId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($budgetId)
|
||||
{
|
||||
|
||||
return \Auth::user()->budgets()->find($budgetId);
|
||||
}
|
||||
|
||||
}
|
@@ -100,7 +100,7 @@ class EloquentLimitRepository implements LimitRepositoryInterface
|
||||
*/
|
||||
public function getTJByBudgetAndDateRange(\Budget $budget, Carbon $start, Carbon $end)
|
||||
{
|
||||
$result = $budget->transactionjournals()->after($start)->before($end)->get();
|
||||
$result = $budget->transactionjournals()->with('transactions')->after($start)->before($end)->get();
|
||||
|
||||
return $result;
|
||||
|
||||
|
@@ -37,6 +37,7 @@ class LimitRepetition extends Ardent
|
||||
$start->startOfMonth();
|
||||
$end = clone $start;
|
||||
$end->endOfMonth();
|
||||
|
||||
return [
|
||||
'limit_id' => 'factory|Limit',
|
||||
'startdate' => $start,
|
||||
@@ -45,11 +46,6 @@ class LimitRepetition extends Ardent
|
||||
];
|
||||
}
|
||||
|
||||
public function limit()
|
||||
{
|
||||
return $this->belongsTo('Limit');
|
||||
}
|
||||
|
||||
public function getDates()
|
||||
{
|
||||
return ['created_at', 'updated_at', 'startdate', 'enddate'];
|
||||
@@ -60,10 +56,6 @@ class LimitRepetition extends Ardent
|
||||
*/
|
||||
public function left()
|
||||
{
|
||||
$key = 'limit-rep-left-' . $this->id;
|
||||
if (Cache::has($key)) {
|
||||
return Cache::get($key);
|
||||
}
|
||||
$left = floatval($this->amount);
|
||||
|
||||
// budget:
|
||||
@@ -80,11 +72,14 @@ class LimitRepetition extends Ardent
|
||||
}
|
||||
}
|
||||
}
|
||||
Cache::forever($key, $left);
|
||||
|
||||
|
||||
return $left;
|
||||
}
|
||||
|
||||
public function limit()
|
||||
{
|
||||
return $this->belongsTo('Limit');
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -3,8 +3,23 @@
|
||||
// models:
|
||||
Route::bind('account', function($value, $route)
|
||||
{
|
||||
return Account::where('id', $value)->where('user_id',Auth::user()->id)->first();
|
||||
if(Auth::check()) {
|
||||
return Account::
|
||||
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;
|
||||
});
|
||||
|
||||
|
||||
|
||||
// protected routes:
|
||||
@@ -15,7 +30,7 @@ Route::group(['before' => 'auth'], function () {
|
||||
Route::get('/flush', ['uses' => 'HomeController@flush', 'as' => 'flush']);
|
||||
|
||||
// chart controller
|
||||
Route::get('/chart/home/account/{account?}', ['uses' => 'ChartController@homeAccount', 'as' => 'chart.home']);
|
||||
Route::get('/chart/home/account/{accountname?}', ['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/info/{accountname}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']);
|
||||
@@ -42,7 +57,9 @@ Route::group(['before' => 'auth'], function () {
|
||||
Route::get('/budget/create',['uses' => 'BudgetController@create', 'as' => 'budgets.create']);
|
||||
Route::get('/budgets',['uses' => 'BudgetController@indexByDate','as' => 'budgets.index']);
|
||||
Route::get('/budgets/budget',['uses' => 'BudgetController@indexByBudget','as' => 'budgets.index.budget']);
|
||||
Route::get('/budget/show/{id}',['uses' => 'BudgetController@show', 'as' => 'budgets.show']);
|
||||
Route::get('/budget/show/{budget}',['uses' => 'BudgetController@show', 'as' => 'budgets.show']);
|
||||
Route::get('/budget/edit/{budget}',['uses' => 'BudgetController@edit', 'as' => 'budgets.edit']);
|
||||
Route::get('/budget/delete/{budget}',['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']);
|
||||
|
||||
// limit controller:
|
||||
Route::get('/budgets/limits/create/{id?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']);
|
||||
|
@@ -1,102 +1,210 @@
|
||||
<?php
|
||||
|
||||
use Mockery as m;
|
||||
use \League\FactoryMuffin\Facade\FactoryMuffin as f;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class AccountControllerTest extends TestCase
|
||||
{
|
||||
protected $_repository;
|
||||
protected $_user;
|
||||
protected $_accounts;
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Artisan::call('migrate');
|
||||
Artisan::call('db:seed');
|
||||
}
|
||||
|
||||
public function testIndex()
|
||||
{
|
||||
// mock account type(s):
|
||||
$personal = $this->mock('AccountType');
|
||||
$personal->shouldReceive('getAttribute', 'description')->andReturn('Default account');
|
||||
|
||||
$bene = $this->mock('AccountType');
|
||||
$bene->shouldReceive('getAttribute', 'description')->andReturn('Beneficiary account');
|
||||
|
||||
$initial = $this->mock('AccountType');
|
||||
$initial->shouldReceive('getAttribute', 'description')->andReturn('Initial balance account');
|
||||
|
||||
$cash = $this->mock('AccountType');
|
||||
$cash->shouldReceive('getAttribute', 'description')->andReturn('Cash account');
|
||||
|
||||
|
||||
// mock account(s)
|
||||
$one = $this->mock('Account');
|
||||
$one->shouldReceive('getAttribute')->andReturn($personal);
|
||||
|
||||
$two = $this->mock('Account');
|
||||
$two->shouldReceive('getAttribute')->andReturn($bene);
|
||||
|
||||
$three = $this->mock('Account');
|
||||
$three->shouldReceive('getAttribute')->andReturn($initial);
|
||||
|
||||
$four = $this->mock('Account');
|
||||
$four->shouldReceive('getAttribute')->andReturn($cash);
|
||||
$c = new \Illuminate\Database\Eloquent\Collection([$one, $two, $three, $four]);
|
||||
|
||||
// mock account repository:
|
||||
$accounts = $this->mock('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts->shouldReceive('get')->andReturn($c);
|
||||
|
||||
|
||||
$list = [
|
||||
'personal' => [$one],
|
||||
'beneficiaries' => [$two],
|
||||
'initial' => [$three],
|
||||
'cash' => [$four]
|
||||
];
|
||||
|
||||
// mock:
|
||||
View::shouldReceive('share');
|
||||
View::shouldReceive('make')->with('accounts.index')->once()->andReturn(\Mockery::self())
|
||||
->shouldReceive('with')->once()->with('accounts', $list)->andReturn(\Mockery::self())
|
||||
->shouldReceive('with')->once()->with('total', 4)->andReturn(\Mockery::self());
|
||||
|
||||
|
||||
// call
|
||||
$this->call('GET', '/accounts');
|
||||
|
||||
// test
|
||||
$this->assertResponseOk();
|
||||
$this->_repository = $this->mock('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$this->_accounts = $this->mock('Firefly\Helper\Controllers\AccountInterface');
|
||||
$this->_user = m::mock('User','Eloquent');
|
||||
$this->app->instance('User', $this->_user);
|
||||
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
// mock:
|
||||
View::shouldReceive('share');
|
||||
View::shouldReceive('make')->with('accounts.create');
|
||||
|
||||
// call
|
||||
$this->call('GET', '/accounts/create');
|
||||
|
||||
// test
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testShow()
|
||||
{
|
||||
// mock account repository:
|
||||
$accounts = $this->mock('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts->shouldReceive('get')->with(1)->andReturn([]);
|
||||
|
||||
// call
|
||||
$this->call('GET', '/accounts/1');
|
||||
|
||||
// test
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$this->action('GET', 'AccountController@create');
|
||||
$this->assertResponseOk();
|
||||
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
|
||||
$account = f::create('Account');
|
||||
Auth::shouldReceive('user')->andReturn($this->_user);
|
||||
Auth::shouldReceive('check')->andReturn(true);
|
||||
$this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id);
|
||||
$this->_user->shouldReceive('getAttribute')->with('email')->once()->andReturn($account->email);
|
||||
|
||||
$this->action('GET', 'AccountController@delete',$account->id);
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testDestroy()
|
||||
{
|
||||
$account = f::create('Account');
|
||||
Auth::shouldReceive('user')->andReturn($this->_user);
|
||||
$this->_repository->shouldReceive('destroy')->once()->with("")->andReturn(true);
|
||||
$this->action('POST', 'AccountController@destroy',$account->id);
|
||||
$this->assertRedirectedToRoute('accounts.index');
|
||||
$this->assertSessionHas('success');
|
||||
}
|
||||
|
||||
public function testDestroyFails()
|
||||
{
|
||||
$account = f::create('Account');
|
||||
$this->_repository->shouldReceive('destroy')->once()->with("")->andReturn(false);
|
||||
$this->action('POST', 'AccountController@destroy',$account->id);
|
||||
$this->assertRedirectedToRoute('accounts.index');
|
||||
$this->assertSessionHas('error');
|
||||
}
|
||||
|
||||
public function testEdit()
|
||||
{
|
||||
$account = f::create('Account');
|
||||
|
||||
Auth::shouldReceive('user')->andReturn($this->_user);
|
||||
Auth::shouldReceive('check')->andReturn(true);
|
||||
$this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id);
|
||||
$this->_user->shouldReceive('getAttribute')->with('email')->once()->andReturn($account->email);
|
||||
$this->_accounts->shouldReceive('openingBalanceTransaction')->once()->andReturn(null);
|
||||
|
||||
$this->action('GET', 'AccountController@edit',$account->id);
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testIndex()
|
||||
{
|
||||
$account = f::create('Account');
|
||||
$collection = new Collection();
|
||||
$collection->add($account);
|
||||
|
||||
$list = [
|
||||
'personal' => [],
|
||||
'beneficiaries' => [],
|
||||
'initial' => [],
|
||||
'cash' => []
|
||||
];
|
||||
|
||||
$this->_repository->shouldReceive('get')->with()->once()->andReturn($collection);
|
||||
$this->_accounts->shouldReceive('index')->with($collection)->once()->andReturn($list);
|
||||
$this->action('GET', 'AccountController@index');
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testShow()
|
||||
{
|
||||
$account = f::create('Account');
|
||||
|
||||
Auth::shouldReceive('user')->andReturn($this->_user);
|
||||
Auth::shouldReceive('check')->andReturn(true);
|
||||
$this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id);
|
||||
$this->_user->shouldReceive('getAttribute')->with('email')->once()->andReturn($account->email);
|
||||
$this->_accounts->shouldReceive('paginate')->with($account,40)->once()->andReturn();
|
||||
|
||||
$this->action('GET', 'AccountController@show',$account->id);
|
||||
$this->assertResponseOk();
|
||||
}
|
||||
|
||||
public function testStore()
|
||||
{
|
||||
// $this->action('POST', 'AccountController@store');
|
||||
// $this->assertResponseOk();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// public function testIndex()
|
||||
// {
|
||||
//// // mock account type(s):
|
||||
//// $personal = $this->mock('AccountType');
|
||||
//// $personal->shouldReceive('getAttribute', 'description')->andReturn('Default account');
|
||||
////
|
||||
//// $bene = $this->mock('AccountType');
|
||||
//// $bene->shouldReceive('getAttribute', 'description')->andReturn('Beneficiary account');
|
||||
////
|
||||
//// $initial = $this->mock('AccountType');
|
||||
//// $initial->shouldReceive('getAttribute', 'description')->andReturn('Initial balance account');
|
||||
////
|
||||
//// $cash = $this->mock('AccountType');
|
||||
//// $cash->shouldReceive('getAttribute', 'description')->andReturn('Cash account');
|
||||
////
|
||||
////
|
||||
//// // mock account(s)
|
||||
//// $one = $this->mock('Account');
|
||||
//// $one->shouldReceive('getAttribute')->andReturn($personal);
|
||||
////
|
||||
//// $two = $this->mock('Account');
|
||||
//// $two->shouldReceive('getAttribute')->andReturn($bene);
|
||||
////
|
||||
//// $three = $this->mock('Account');
|
||||
//// $three->shouldReceive('getAttribute')->andReturn($initial);
|
||||
////
|
||||
//// $four = $this->mock('Account');
|
||||
//// $four->shouldReceive('getAttribute')->andReturn($cash);
|
||||
//// $c = new \Illuminate\Database\Eloquent\Collection([$one, $two, $three, $four]);
|
||||
////
|
||||
//// // mock account repository:
|
||||
//// $accounts = $this->mock('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
//// $accounts->shouldReceive('get')->andReturn($c);
|
||||
////
|
||||
////
|
||||
//// $list = [
|
||||
//// 'personal' => [$one],
|
||||
//// 'beneficiaries' => [$two],
|
||||
//// 'initial' => [$three],
|
||||
//// 'cash' => [$four]
|
||||
//// ];
|
||||
////
|
||||
//// // mock:
|
||||
//// View::shouldReceive('share');
|
||||
//// View::shouldReceive('make')->with('accounts.index')->once()->andReturn(\Mockery::self())
|
||||
//// ->shouldReceive('with')->once()->with('accounts', $list)->andReturn(\Mockery::self())
|
||||
//// ->shouldReceive('with')->once()->with('total', 4)->andReturn(\Mockery::self());
|
||||
////
|
||||
//
|
||||
// // call
|
||||
// $this->call('GET', '/accounts');
|
||||
//
|
||||
// // test
|
||||
// $this->assertResponseOk();
|
||||
//
|
||||
// }
|
||||
////
|
||||
//// public function testCreate()
|
||||
//// {
|
||||
//// // mock:
|
||||
//// View::shouldReceive('share');
|
||||
//// View::shouldReceive('make')->with('accounts.create');
|
||||
////
|
||||
//// // call
|
||||
//// $this->call('GET', '/accounts/create');
|
||||
////
|
||||
//// // test
|
||||
//// $this->assertResponseOk();
|
||||
//// }
|
||||
////
|
||||
//// public function testShow()
|
||||
//// {
|
||||
//// // mock account repository:
|
||||
//// $accounts = $this->mock('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
//// $accounts->shouldReceive('get')->with(1)->andReturn([]);
|
||||
////
|
||||
//// // call
|
||||
//// $this->call('GET', '/accounts/1');
|
||||
////
|
||||
//// // test
|
||||
//// $this->assertResponseOk();
|
||||
//// }
|
||||
////
|
||||
|
||||
public function testUpdate()
|
||||
{
|
||||
}
|
||||
}
|
@@ -48,8 +48,3 @@
|
||||
</div>
|
||||
|
||||
@stop
|
||||
@section('scripts')
|
||||
<script src="https://www.google.com/jsapi"></script>
|
||||
<!-- <script src="assets/javascript/charts.js"></script>-->
|
||||
<!-- <script src="assets/javascript/index.js"></script>-->
|
||||
@stop
|
@@ -18,18 +18,96 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h4>Transactions</h4>
|
||||
<h4>Summary <small>For selected account and period</small></h4>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
|
||||
<table class="table table-striped table-condensed">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Expense / income</th>
|
||||
<th>Transfers</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Out</td>
|
||||
<td>
|
||||
{{mf($show['statistics']['period']['out'])}}
|
||||
<a href="#transactions-thisaccount-this-period-expensesonly"><span class="glyphicon glyphicon-circle-arrow-right"></span></a>
|
||||
</td>
|
||||
<td>
|
||||
{{mf($show['statistics']['period']['t_out'])}}
|
||||
<a href="#transactions-thisaccount-this-period-transfers-out-only"><span class="glyphicon glyphicon-circle-arrow-right"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>In</td>
|
||||
<td>
|
||||
{{mf($show['statistics']['period']['in'])}}
|
||||
<a href="#transactions-thisaccount-this-period-incomeonly"><span class="glyphicon glyphicon-circle-arrow-right"></span></a>
|
||||
</td>
|
||||
<td>
|
||||
{{mf($show['statistics']['period']['t_in'])}}
|
||||
<a href="#transactions-thisaccount-this-period-transfers-in-only"><span class="glyphicon glyphicon-circle-arrow-right"></span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Difference</td>
|
||||
<td>{{mf($show['statistics']['period']['diff'])}}</td>
|
||||
<td>{{mf($show['statistics']['period']['t_diff'])}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
<table class="table table-striped table-condensed">
|
||||
<tr>
|
||||
<td style="width:30%;">Related accounts</td>
|
||||
<td>
|
||||
@foreach($show['statistics']['accounts'] as $acct)
|
||||
<a href="{{route('accounts.show',$acct->id)}}" class="btn btn-default btn-xs">{{{$acct->name}}}</a>
|
||||
@endforeach
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Related categories</td>
|
||||
<td>
|
||||
@foreach($show['statistics']['categories'] as $cat)
|
||||
<a href="#category-overview" class="btn btn-default btn-xs">{{{$cat->name}}}</a>
|
||||
@endforeach
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Related budgets</td>
|
||||
<td>
|
||||
@foreach($show['statistics']['budgets'] as $bud)
|
||||
<a href="#budget-overview" class="btn btn-default btn-xs">{{{$bud->name}}}</a>
|
||||
@endforeach
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
|
||||
|
||||
<h4>Transactions <small> For selected account and period</small></h4>
|
||||
@include('paginated.transactions',['journals' => $show['journals']])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stop
|
||||
|
||||
@section('styles')
|
||||
<?php echo stylesheet_link_tag('accounts'); ?>
|
||||
@stop
|
||||
|
||||
@section('scripts')
|
||||
<script type="text/javascript">
|
||||
var accountID = {{$account->id}};
|
||||
</script>
|
||||
<script src="assets/javascript/highcharts.js"></script>
|
||||
<script src="assets/javascript/highcharts-more.js"></script>
|
||||
<script src="assets/javascript/highslide-full.min.js"></script>
|
||||
<script src="assets/javascript/highslide.config.js"></script>
|
||||
<script src="assets/javascript/accounts.js"></script>
|
||||
<?php echo javascript_include_tag('accounts'); ?>
|
||||
@stop
|
@@ -3,18 +3,29 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h1>Firefly
|
||||
<small>Budgets and limits</small>
|
||||
<small>Budgets and envelopes</small>
|
||||
</h1>
|
||||
<p class="lead">Use budgets to organize and limit your expenses.</p>
|
||||
|
||||
<p class="text-info">
|
||||
These are your budgets and if set, their "limits". Firefly uses an "<a
|
||||
href="http://en.wikipedia.org/wiki/Envelope_System" class="text-success">envelope system</a>" for your
|
||||
budgets,
|
||||
which means that for each period of time (for example a month) a virtual "envelope" can be created
|
||||
containing a certain amount of money. Money spent within a budget is removed from the envelope.
|
||||
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.
|
||||
<a href="http://dictionary.reference.com/browse/budget">By definition</a>, budgets are an estimated amount
|
||||
of money, ie. € 400,-. Such an estimation can change over time but <em>should</em> always be set. Budgets
|
||||
without an actual budget are fairly pointless.
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-default" href ="{{route('budgets.index')}}"><span class="glyphicon glyphicon-th"></span> Group by date</a>
|
||||
<a class="btn btn-default" href ="{{route('budgets.limits.create')}}"><span class="glyphicon glyphicon-plus-sign"></span> Create a limit</a>
|
||||
<p class="text-info">
|
||||
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.
|
||||
</p>
|
||||
<p class="text-info">
|
||||
* <small>Every month, week, year, etc.</small>
|
||||
</p>
|
||||
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default" href ="{{route('budgets.index')}}"><span class="glyphicon glyphicon-th"></span> Group budgets by date</a>
|
||||
<a class="btn btn-default" href ="{{route('budgets.limits.create')}}"><span class="glyphicon glyphicon-plus-sign"></span> Create a new envelope</a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,7 +35,7 @@
|
||||
<tr>
|
||||
<th>Budget</th>
|
||||
<th>Current envelope(s)</th>
|
||||
<th> </th>
|
||||
<th>Update budget</th>
|
||||
</tr>
|
||||
@foreach($budgets as $budget)
|
||||
<tr>
|
||||
@@ -50,14 +61,14 @@
|
||||
{{mf($rep->amount,false)}}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
@if($rep->left() < 0)
|
||||
@if($rep->left < 0)
|
||||
<span class="label label-danger">
|
||||
<span class="glyphicon glyphicon-envelope"></span>
|
||||
{{mf($rep->left(),false)}}</span>
|
||||
{{mf($rep->left,false)}}</span>
|
||||
@else
|
||||
<span class="label label-success">
|
||||
<span class="glyphicon glyphicon-envelope"></span>
|
||||
{{mf($rep->left(),false)}}</span>
|
||||
{{mf($rep->left,false)}}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
@@ -75,12 +86,14 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-sm-2 @if($limit->repeats == 0) col-sm-offset-2 @endif">
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a href="#" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||
@if($limit->repeats == 0 || ($limit->repeats == 1 && $index == 0))
|
||||
<a href="#" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
<p style="margin-top:5px;">
|
||||
|
@@ -3,64 +3,82 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h1>Firefly
|
||||
<small>Budgets and limits</small>
|
||||
<small>Budgets and envelopes</small>
|
||||
</h1>
|
||||
<p class="lead">Use budgets to organize and limit your expenses.</p>
|
||||
|
||||
<p class="text-info">
|
||||
These are your budgets and if set, their "limits". Firefly uses an "<a
|
||||
href="http://en.wikipedia.org/wiki/Envelope_System" class="text-success">envelope system</a>" for your
|
||||
budgets,
|
||||
which means that for each period of time (for example a month) a virtual "envelope" can be created
|
||||
containing a certain amount of money. Money spent within a budget is removed from the envelope.
|
||||
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.
|
||||
<a href="http://dictionary.reference.com/browse/budget">By definition</a>, budgets are an estimated amount
|
||||
of money, ie. € 400,-. Such an estimation can change over time but <em>should</em> always be set. Budgets
|
||||
without an actual budget are fairly pointless.
|
||||
</p>
|
||||
<p class="text-info">
|
||||
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.
|
||||
</p>
|
||||
<p class="text-info">
|
||||
* <small>Every month, week, year, etc.</small>
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-default" href ="{{route('budgets.index.budget')}}"><span class="glyphicon glyphicon-indent-left"></span> Group by budget</a>
|
||||
<a class="btn btn-default" href ="{{route('budgets.limits.create')}}"><span class="glyphicon glyphicon-plus-sign"></span> Create a limit</a>
|
||||
<a class="btn btn-default" href ="{{route('budgets.limits.create')}}"><span class="glyphicon glyphicon-plus-sign"></span> Create an envelope</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach($reps as $date => $data)
|
||||
<!-- count = zero! -->
|
||||
|
||||
@foreach($budgets as $date => $entry)
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h3>{{$data['date']}}</h3>
|
||||
<h3><a href="#transactions-in-this-period">{{$entry['date']}}</a>
|
||||
<a class="btn btn-default btn-xs" href ="{{route('budgets.limits.create')}}#date-and-budget-selected"><span class="glyphicon glyphicon-plus-sign"></span> Create an envelope for {{$entry['date']}}</a>
|
||||
</h3>
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr>
|
||||
<th style="width:45%;">Budget</th>
|
||||
<th colspan="2" style="width:45%;">Budget</th>
|
||||
<th style="width:15%;">Envelope</th>
|
||||
<th style="width:15%;">Left</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
@foreach($data['limitrepetitions'] as $index => $rep)
|
||||
@foreach($entry['limitrepetitions'] as $index => $repetition)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{route('budgets.show',$rep->limit->budget->id)}}">{{{$rep->limit->budget->name}}}</a>
|
||||
|
||||
<div class="btn-group">
|
||||
<a title="Edit budget {{{$repetition->limit->budget->name}}}" href="{{route('budgets.edit',$repetition->limit->budget->id)}}" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||
<a title="Delete budget {{{$repetition->limit->budget->name}}}" href="{{route('budgets.delete',$repetition->limit->budget->id)}}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{route('budgets.show',$repetition->limit->budget->id)}}#transactions-in-this-period">
|
||||
{{{$repetition->limit->budget->name}}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="label label-primary">
|
||||
<span class="glyphicon glyphicon-envelope"></span> {{mf($rep->amount,false)}}</span>
|
||||
<span class="glyphicon glyphicon-envelope"></span> {{mf($repetition->amount,false)}}</span>
|
||||
</td>
|
||||
<td>
|
||||
@if($rep->left() < 0)
|
||||
@if($repetition->left < 0)
|
||||
<span class="label label-danger">
|
||||
<span class="glyphicon glyphicon-envelope"></span>
|
||||
{{mf($rep->left(),false)}}</span>
|
||||
{{mf($repetition->left,false)}}</span>
|
||||
@else
|
||||
<span class="label label-success">
|
||||
<span class="glyphicon glyphicon-envelope"></span>
|
||||
{{mf($rep->left(),false)}}</span>
|
||||
{{mf($repetition->left,false)}}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a href="{{route('budgets.limits.edit',$rep->limit->id)}}" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||
<a href="{{route('budgets.limits.delete',$rep->limit->id)}}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
<a title="Edit envelope for {{{$repetition->limit->budget->name}}} in {{$entry['date']}}" href="{{route('budgets.limits.edit',$repetition->limit->id)}}" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||
<a title="Delete envelope for {{{$repetition->limit->budget->name}}} in {{$entry['date']}}" href="{{route('budgets.limits.delete',$repetition->limit->id)}}" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
|
||||
</div>
|
||||
@if($rep->limit->repeats == 1)
|
||||
@if($repetition->limit->repeats == 1)
|
||||
<span class="label label-warning">auto repeats</span>
|
||||
@endif
|
||||
<a href="{{route('budgets.limits.create',$rep->limit->budget->id)}}" class="btn btn-default btn-xs"><span
|
||||
class="glyphicon-plus-sign glyphicon"></span> Add another limit</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
@@ -56,7 +56,7 @@
|
||||
@foreach($set as $data)
|
||||
<div class="col-lg-{{$split}} col-md-{{$split}}">
|
||||
<h4>
|
||||
<a href="{{route('accounts.show',$data[1]->id)}}?range={{Config::get('firefly.range_to_text.'.Session::get('range'))}}&startdate={{Session::get('start')->format('Y-m-d')}}">{{{$data[1]->name}}}</a>
|
||||
<a href="{{route('accounts.show',$data[1]->id)}}">{{{$data[1]->name}}}</a>
|
||||
</h4>
|
||||
|
||||
@include('transactions.journals-small',['transactions' => $data[0],'account' => $data[1]])
|
||||
|
71
app/views/paginated/transactions.blade.php
Normal file
71
app/views/paginated/transactions.blade.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<table class="table table-striped table-condensed">
|
||||
<tr>
|
||||
<th colspan="2"></th>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
<th>Amount (€)</th>
|
||||
<th>From</th>
|
||||
<th>To</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@foreach($journals as $journal)
|
||||
<tr>
|
||||
<td>
|
||||
@if($journal->transactiontype->type == 'Withdrawal')
|
||||
<span class="glyphicon glyphicon-arrow-left" title="Withdrawal"></span>
|
||||
@endif
|
||||
@if($journal->transactiontype->type == 'Deposit')
|
||||
<span class="glyphicon glyphicon-arrow-right" title="Deposit"></span>
|
||||
@endif
|
||||
@if($journal->transactiontype->type == 'Transfer')
|
||||
<span class="glyphicon glyphicon-resize-full" title="Transfer"></span>
|
||||
@endif
|
||||
@if($journal->transactiontype->type == 'Opening balance')
|
||||
<span class="glyphicon glyphicon-ban-circle" title="Opening balance"></span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@foreach($journal->components as $component)
|
||||
@if($component->class == 'Budget')
|
||||
<a href="#budget-overview"><span class="glyphicon glyphicon-tasks" title="Budget: {{{$component->name}}}"></span></a>
|
||||
@endif
|
||||
@if($component->class == 'Category')
|
||||
<a href="#category-overview"><span class="glyphicon glyphicon-tag" title="Category: {{{$component->name}}}"></span></a>
|
||||
@endif
|
||||
@endforeach
|
||||
</td>
|
||||
<td>
|
||||
{{$journal->date->format('d F Y')}}
|
||||
</td>
|
||||
<td><a href="{{route('transactions.show',$journal->id)}}" title="{{{$journal->description}}}">{{{$journal->description}}}</a></td>
|
||||
<td>
|
||||
@if($journal->transactiontype->type == 'Withdrawal')
|
||||
<span class="text-danger">{{mf($journal->transactions[1]->amount,false)}}</span>
|
||||
@endif
|
||||
@if($journal->transactiontype->type == 'Deposit')
|
||||
<span class="text-success">{{mf($journal->transactions[1]->amount,false)}}</span>
|
||||
@endif
|
||||
@if($journal->transactiontype->type == 'Transfer')
|
||||
<span class="text-info">{{mf($journal->transactions[1]->amount,false)}}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{route('accounts.show',$journal->transactions[0]->account_id)}}">{{{$journal->transactions[0]->account->name}}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{route('accounts.show',$journal->transactions[1]->account_id)}}">{{{$journal->transactions[1]->account->name}}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a href="{{route('transactions.edit',$journal->id)}}" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
<a href="{{route('transactions.delete',$journal->id)}}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
|
||||
{{$journals->links()}}
|
@@ -2,7 +2,7 @@
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="bootstrap/autoload.php"
|
||||
colors="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
|
Reference in New Issue
Block a user