New event to create budget limits, new handler to handle said event, new routes for budget control, new routes for limit control, extended migration, extended models, extended JS. [skip-ci]

This commit is contained in:
James Cole
2014-07-20 18:24:27 +02:00
parent 0bcda34738
commit 08cbd91dd9
42 changed files with 1482 additions and 121 deletions

View File

@@ -0,0 +1,97 @@
<?php
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
class BudgetController extends BaseController
{
protected $_budgets;
public function __construct(BRI $budgets)
{
$this->_budgets = $budgets;
View::share('menu', 'budgets');
}
public function index()
{
$budgets = $this->_budgets->get();
$today = new \Carbon\Carbon;
return View::make('budgets.index')->with('budgets', $budgets)->with('today', $today);
}
public function create()
{
$periods = [
'weekly' => 'A week',
'monthly' => 'A month',
'quarterly' => 'A quarter',
'half-year' => 'Six months',
'yearly' => 'A year',
];
return View::make('budgets.create')->with('periods', $periods);
}
public function store()
{
$data = [
'name' => Input::get('name'),
'amount' => floatval(Input::get('amount')),
'repeat_freq' => Input::get('period'),
'repeats' => intval(Input::get('repeats'))
];
$budget = $this->_budgets->create($data);
Session::flash('success', 'Budget created!');
return Redirect::route('budgets.index');
}
public function show($budgetId)
{
$budget = $this->_budgets->find($budgetId);
$list = $budget->transactionjournals()->get();
$return = [];
/** @var \TransactionJournal $entry */
foreach ($list as $entry) {
$month = $entry->date->format('F Y');
$return[$month] = isset($return[$month]) ? $return[$month] : [];
$return[$month][] = $entry;
}
foreach ($return as $month => $set) {
echo '<h1>' . $month . '</h1>';
/** @var \TransactionJournal $tj */
$sum = 0;
foreach ($set as $tj) {
echo '#' . $tj->id . ' ' . $tj->description . ': ';
foreach ($tj->transactions as $index => $t) {
echo $t->amount . ', ';
if ($index == 0) {
$sum += $t->amount;
}
}
echo '<br>';
}
echo 'sum: ' . $sum . '<br><br>';
}
exit;
return View::make('budgets.show');
}
}

View File

@@ -23,6 +23,9 @@ class HomeController extends BaseController
$this->_preferences = $preferences;
$this->_journal = $journal;
View::share('menu', 'home');
}
/**
@@ -30,10 +33,28 @@ class HomeController extends BaseController
*/
public function index()
{
// get the accounts to display on the home screen:
// count, maybe we need some introductionary text to show:
$count = $this->_accounts->count();
// get the preference for the home accounts to show:
$frontpage = $this->_preferences->get('frontpageAccounts', []);
$accounts = $this->_accounts->getByIds($frontpage->data);
$transactions = [];
foreach($accounts as $account) {
$transactions[] = [$this->_journal->getByAccount($account,15),$account];
}
if(count($transactions) % 2 == 0) {
$transactions = array_chunk($transactions, 2);
} elseif(count($transactions) == 1) {
$transactions = array_chunk($transactions, 3);
} else {
$transactions = array_chunk($transactions, 3);
}
// build the home screen:
return View::make('index')->with('count', $count);
return View::make('index')->with('count', $count)->with('transactions',$transactions);
}
}

View File

@@ -0,0 +1,49 @@
<?php
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
use Firefly\Storage\Limit\LimitRepositoryInterface as LRI;
class LimitController extends BaseController
{
protected $_budgets;
protected $_limits;
public function __construct(BRI $budgets, LRI $limits)
{
$this->_budgets = $budgets;
$this->_limits = $limits;
View::share('menu', 'budgets');
}
public function create($budgetId = null)
{
$periods = [
'weekly' => 'A week',
'monthly' => 'A month',
'quarterly' => 'A quarter',
'half-year' => 'Six months',
'yearly' => 'A year',
];
$budget = $this->_budgets->find($budgetId);
$budget_id = is_null($budget) ? null : $budget->id;
$budgets = $this->_budgets->getAsSelectList();
return View::make('limits.create')->with('budgets', $budgets)->with('budget_id', $budget_id)->with(
'periods', $periods
);
}
public function store()
{
// find a limit with these properties, as we might already have one:
$limit = $this->_limits->store(Input::all());
if($limit->id) {
return Redirect::route('budgets.index');
} else {
return Redirect::route('budgets.limits.create')->withInput();
}
}
}

View File

@@ -1,5 +1,6 @@
<?php
use Carbon\Carbon as Carbon;
use Firefly\Helper\Migration\MigrationHelperInterface as MHI;
/**
@@ -40,10 +41,123 @@ class MigrationController extends BaseController
exit();
}
}
echo '<a href="'.route('index').'">home</a>';
echo '<a href="' . route('index') . '">home</a>';
exit();
}
public function limit()
{
$user = User::find(1);
$budgets = [];
// new budget
for ($i = 0; $i < 7; $i++) {
$budget = new Budget();
$budget->user()->associate($user);
$budget->name = 'Some budget #' . rand(1, 2000);
$budget->save();
$budgets[] = $budget;
}
// create a non-repeating limit for this week:
$today = new Carbon('01-07-2014');
$limit = new Limit;
$limit->budget()->associate($budgets[0]);
$limit->amount = 100;
$limit->startdate = $today;
$limit->amount = 100;
$limit->repeats = 0;
$limit->repeat_freq = 'weekly';
var_dump($limit->save());
var_dump($limit->errors()->all());
// create a repeating daily limit:
$day = new Limit;
$day->budget()->associate($budgets[1]);
$day->amount = 100;
$day->startdate = $today;
$day->amount = 100;
$day->repeats = 1;
$day->repeat_freq = 'daily';
$day->save();
// repeating weekly limit.
$week = new Limit;
$week->budget()->associate($budgets[2]);
$week->amount = 100;
$week->startdate = $today;
$week->amount = 100;
$week->repeats = 1;
$week->repeat_freq = 'weekly';
$week->save();
// repeating monthly limit
$month = new Limit;
$month->budget()->associate($budgets[3]);
$month->amount = 100;
$month->startdate = $today;
$month->amount = 100;
$month->repeats = 1;
$month->repeat_freq = 'monthly';
$month->save();
// quarter
$quarter = new Limit;
$quarter->budget()->associate($budgets[4]);
$quarter->amount = 100;
$quarter->startdate = $today;
$quarter->amount = 100;
$quarter->repeats = 1;
$quarter->repeat_freq = 'quarterly';
$quarter->save();
// six months
$six = new Limit;
$six->budget()->associate($budgets[5]);
$six->amount = 100;
$six->startdate = $today;
$six->amount = 100;
$six->repeats = 1;
$six->repeat_freq = 'half-year';
$six->save();
// year
$yearly = new Limit;
$yearly->budget()->associate($budgets[6]);
$yearly->amount = 100;
$yearly->startdate = $today;
$yearly->amount = 100;
$yearly->repeats = 1;
$yearly->repeat_freq = 'yearly';
$yearly->save();
// create a repeating weekly limit:
// create a repeating monthly limit:
foreach ($budgets as $budget) {
echo '#' . $budget->id . ': ' . $budget->name . ':<br />';
foreach ($budget->limits()->get() as $limit) {
echo '&nbsp;&nbsp;Limit #' . $limit->id . ', amount: ' . $limit->amount . ', start: '
. $limit->startdate->format('D d-m-Y') . ', repeats: '
. $limit->repeats . ', repeat_freq: ' . $limit->repeat_freq . '<br />';
foreach ($limit->limitrepetitions()->get() as $rep) {
echo '&nbsp;&nbsp;&nbsp;&nbsp;rep: #' . $rep->id . ', from ' . $rep->startdate->format('D d-m-Y')
. ' to '
. $rep->enddate->format('D d-m-Y') . '<br>';
}
}
}
return '';
}
/**
* @return \Illuminate\View\View
*/

View File

@@ -18,8 +18,9 @@ class CreateLimitsTable extends Migration {
$table->timestamps();
$table->integer('component_id')->unsigned();
$table->date('startdate');
$table->date('enddate');
$table->decimal('amount',10,2);
$table->boolean('repeats');
$table->enum('repeat_freq', ['daily', 'weekly','monthly','quarterly','half-year','yearly']);
// connect component
$table->foreign('component_id')

View File

@@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLimitRepeatTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('limit_repetitions', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->integer('limit_id')->unsigned();
$table->date('startdate');
$table->date('enddate');
$table->decimal('amount',10,2);
$table->unique(['limit_id','startdate','enddate']);
// connect limit
$table->foreign('limit_id')
->references('id')->on('limits')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('limit_repetitions');
}
}

View File

@@ -7,6 +7,7 @@ App::before(
if (Auth::check()) {
\Firefly\Helper\Toolkit\Toolkit::getDateRange();
}
Event::fire('app.before');
}
);

View File

@@ -3,6 +3,8 @@
namespace Firefly\Helper\Migration;
use Firefly\Helper\MigrationException;
class MigrationHelper implements MigrationHelperInterface
{
protected $path;
@@ -56,6 +58,9 @@ class MigrationHelper implements MigrationHelperInterface
// create transfers:
$this->_importTransfers();
// create limits:
$this->_importLimits();
} catch (\Firefly\Exception\FireflyException $e) {
\DB::rollBack();
@@ -75,7 +80,7 @@ class MigrationHelper implements MigrationHelperInterface
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$cash = $accounts->store(['name' => 'Cash account', 'account_type' => $cashAT, 'active' => 0]);
\Log::info('Created cash account (#'.$cash->id.')');
\Log::info('Created cash account (#' . $cash->id . ')');
$this->map['cash'] = $cash;
}
@@ -149,6 +154,39 @@ class MigrationHelper implements MigrationHelperInterface
return $components->store(['name' => $component->name, 'class' => 'Budget']);
}
protected function _importLimits()
{
\Log::info('Importing limits');
foreach ($this->JSON->limits as $entry) {
\Log::debug(
'Now at #' . $entry->id . ': EUR ' . $entry->amount . ' for month ' . $entry->date
. ' and componentID: ' . $entry->component_id
);
$budget = isset($this->map['budgets'][$entry->component_id]) ? $this->map['budgets'][$entry->component_id]
: null;
if (!is_null($budget)) {
\Log::debug('Found budget for this limit: #' . $budget->id . ', ' . $budget->name);
$limit = new \Limit;
$limit->budget()->associate($budget);
$limit->startdate = new \Carbon\Carbon($entry->date);
$limit->amount = floatval($entry->amount);
$limit->repeats = 0;
$limit->repeat_freq = 'monthly';
if (!$limit->save()) {
\Log::error('MigrationException!');
throw new MigrationException('Importing limits failed: ' . $limit->errors()->first());
}
} else {
\Log::warning('No budget for this limit!');
}
// create repeat thing should not be necessary.
}
}
protected function _importTransactions()
{

View File

@@ -13,7 +13,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
public function get()
{
return \Auth::user()->accounts()->with('accounttype')->orderBy('name','ASC')->get();
return \Auth::user()->accounts()->with('accounttype')->orderBy('name', 'ASC')->get();
}
public function getBeneficiaries()
@@ -23,7 +23,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
)
->where('account_types.description', 'Beneficiary account')->where('accounts.active', 1)
->orderBy('accounts.name','ASC')->get(['accounts.*']);
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
return $list;
}
@@ -34,7 +34,11 @@ class EloquentAccountRepository implements AccountRepositoryInterface
public function getByIds($ids)
{
return \Auth::user()->accounts()->with('accounttype')->whereIn('id', $ids)->orderBy('name','ASC')->get();
if (count($ids) > 0) {
return \Auth::user()->accounts()->with('accounttype')->whereIn('id', $ids)->orderBy('name', 'ASC')->get();
} else {
return [];
}
}
public function getDefault()
@@ -42,7 +46,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return \Auth::user()->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->where('account_types.description', 'Default account')
->orderBy('accounts.name','ASC')->get(['accounts.*']);
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
}
public function getActiveDefault()
@@ -60,7 +64,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
)
->where('account_types.description', 'Default account')->where('accounts.active', 1)
->orderBy('accounts.name','ASC')->get(['accounts.*']);
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
$return = [];
foreach ($list as $entry) {
$return[intval($entry->id)] = $entry->name;

View File

@@ -6,6 +6,9 @@ namespace Firefly\Storage\Budget;
interface BudgetRepositoryInterface
{
public function getAsSelectList();
public function get();
public function create($data);
public function find($id);

View File

@@ -8,7 +8,9 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
public function getAsSelectList()
{
$list = \Auth::user()->budgets()->get();
$list = \Auth::user()->budgets()->with(
['limits', 'limits.limitrepetitions']
)->orderBy('name', 'ASC')->get();
$return = [];
foreach ($list as $entry) {
$return[intval($entry->id)] = $entry->name;
@@ -16,6 +18,63 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
return $return;
}
public function create($data)
{
$budget = new \Budget;
$budget->name = $data['name'];
$budget->user()->associate(\Auth::user());
$budget->save();
// if limit, create limit (repetition itself will be picked up elsewhere).
if ($data['amount'] > 0) {
$limit = new \Limit;
$limit->budget()->associate($budget);
$startDate = new \Carbon\Carbon;
switch ($data['repeat_freq']) {
case 'daily':
$startDate->startOfDay();
break;
case 'weekly':
$startDate->startOfWeek();
break;
case 'monthly':
$startDate->startOfMonth();
break;
case 'quarterly':
$startDate->firstOfQuarter();
break;
case 'half-year':
$startDate->startOfYear();
if (intval($startDate->format('m')) >= 7) {
$startDate->addMonths(6);
}
break;
case 'yearly':
$startDate->startOfYear();
break;
}
$limit->startdate = $startDate;
$limit->amount = $data['amount'];
$limit->repeats = $data['repeats'];
$limit->repeat_freq = $data['repeat_freq'];
$limit->save();
}
return $budget;
}
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();
}
public function find($id)
{

View File

@@ -0,0 +1,84 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 20/07/14
* Time: 13:43
*/
namespace Firefly\Storage\Limit;
class EloquentLimitRepository implements LimitRepositoryInterface
{
public function store($data)
{
$budget = \Budget::find($data['budget_id']);
if (is_null($budget)) {
\Session::flash('error', 'No such budget.');
return new \Limit;
}
// set the date to the correct start period:
$date = new \Carbon\Carbon($data['startdate']);
switch ($data['period']) {
case 'daily':
$date->startOfDay();
break;
case 'weekly':
$date->startOfWeek();
break;
case 'monthly':
$date->startOfMonth();
break;
case 'quarterly':
$date->firstOfQuarter();
break;
case 'half-year':
if (intval($date->format('m')) >= 7) {
$date->startOfYear();
$date->addMonths(6);
} else {
$date->startOfYear();
}
break;
case 'yearly':
$date->startOfYear();
break;
}
// find existing:
$count = \Limit::
leftJoin('components', 'components.id', '=', 'limits.component_id')->where(
'components.user_id', \Auth::user()->id
)->where('startdate', $date->format('Y-m-d'))->where('component_id', $data['budget_id'])->where(
'repeat_freq', $data['period']
)->count();
if ($count > 0) {
\Session::flash('error', 'There already is an entry for these parameters.');
return new \Limit;
}
// create new limit:
$limit = new \Limit;
$limit->budget()->associate($budget);
$limit->startdate = $date;
$limit->amount = floatval($data['amount']);
$limit->repeats = isset($data['repeats']) ? intval($data['repeats']) : 0;
$limit->repeat_freq = $data['period'];
if (!$limit->save()) {
Session::flash('error', 'Could not save: ' . $limit->errors()->first());
}
return $limit;
}
public function getTJByBudgetAndDateRange(\Budget $budget, \Carbon\Carbon $start, \Carbon\Carbon $end)
{
$type = \TransactionType::where('type', 'Withdrawal')->first();
$result = $budget->transactionjournals()->after($start)->
before($end)->get();
return $result;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Firefly\Storage\Limit;
interface LimitRepositoryInterface
{
public function store($data);
public function getTJByBudgetAndDateRange(\Budget $budget, \Carbon\Carbon $start, \Carbon\Carbon $end);
}

View File

@@ -34,6 +34,11 @@ class StorageServiceProvider extends ServiceProvider
'Firefly\Storage\Component\EloquentComponentRepository'
);
$this->app->bind(
'Firefly\Storage\Limit\LimitRepositoryInterface',
'Firefly\Storage\Limit\EloquentLimitRepository'
);
$this->app->bind(
'Firefly\Storage\Budget\BudgetRepositoryInterface',
'Firefly\Storage\Budget\EloquentBudgetRepository'

View File

@@ -0,0 +1,178 @@
<?php
namespace Firefly\Trigger\Limits;
/**
* Class EloquentLimitTrigger
*
* @package Firefly\Trigger\Limits
*/
class EloquentLimitTrigger
{
public function updateLimitRepetitions()
{
if (!\Auth::check()) {
return;
}
// get budgets with limits:
$budgets = \Auth::user()->budgets()
->with(['limits', 'limits.limitrepetitions'])
->whereNotNull('limits.id')
->leftJoin('limits', 'components.id', '=', 'limits.component_id')->get(['components.*']);
// get todays date.
foreach ($budgets as $budget) {
\Log::debug(
'Now checking the ' . count($budget->limits) . ' limits in ' . $budget->name . ' (#' . $budget->id
. ').'
);
// loop limits:
foreach ($budget->limits as $limit) {
\Log::debug(
'Now at limit #' . $limit->id . ', which has ' . count($limit->limitrepetitions) . ' reps already'
);
\Log::debug(
'More: Amount: ' . $limit->amount . ', repeat: ' . $limit->repeats . ', freq: '
. $limit->repeat_freq
);
// should have a repetition, at the very least
// for the period it starts (startdate and onwards).
if (count($limit->limitrepetitions) == 0) {
\Log::debug('No reps, create one.');
// create such a repetition:
$repetition = new \LimitRepetition();
$start = clone $limit->startdate;
$end = clone $start;
// go to end:
switch ($limit->repeat_freq) {
case 'daily':
$end->addDay();
break;
case 'weekly':
$end->addWeek();
break;
case 'monthly':
$end->addMonth();
break;
case 'quarterly':
$end->addMonths(3);
break;
case 'half-year':
$end->addMonths(6);
break;
case 'yearly':
$end->addYear();
break;
}
$end->subDay();
$repetition->startdate = $start;
$repetition->enddate = $end;
$repetition->amount = $limit->amount;
$repetition->limit()->associate($limit);
\Log::debug('Created single rep for non-repeating limit, from ' . $start . ' until ' . $end);
try {
$repetition->save();
} catch (\Illuminate\Database\QueryException $e) {
// do nothing
\Log::error($e->getMessage());
}
} else {
// there are limits already, do they
// fall into the range surrounding today?
$today = new \Carbon\Carbon;
$today->addMonths(2);
if ($limit->repeats == 1 && $today >= $limit->startdate) {
/** @var \Carbon\Carbon $flowStart */
$flowStart = clone $today;
/** @var \Carbon\Carbon $flowEnd */
$flowEnd = clone $today;
switch ($limit->repeat_freq) {
case 'daily':
$flowStart->startOfDay();
$flowEnd->endOfDay();
break;
case 'weekly':
$flowStart->startOfWeek();
$flowEnd->endOfWeek();
break;
case 'monthly':
$flowStart->startOfMonth();
$flowEnd->endOfMonth();
break;
case 'quarterly':
$flowStart->firstOfQuarter();
$flowEnd->startOfMonth()->lastOfQuarter()->endOfDay();
break;
case 'half-year':
if (intval($flowStart->format('m')) >= 7) {
$flowStart->startOfYear();
$flowStart->addMonths(6);
} else {
$flowStart->startOfYear();
}
$flowEnd->endOfYear();
if (intval($start->format('m')) <= 6) {
$flowEnd->subMonths(6);
$flowEnd->subDay();
}
break;
case 'yearly':
$flowStart->startOfYear();
$flowEnd->endOfYear();
break;
}
$inRange = false;
foreach ($limit->limitrepetitions as $rep) {
if ($rep->startdate->format('dmY') == $flowStart->format('dmY')
&& $rep->enddate->format('dmY') == $flowEnd->format('dmY')
) {
// falls in current range, do nothing?
$inRange = true;
}
}
// if there is none that fall in range, create!
if ($inRange === false) {
// create (but check first)!
$count = \LimitRepetition::where('limit_id', $limit->id)->where('startdate', $flowStart)
->where('enddate', $flowEnd)->count();
if ($count == 0) {
$repetition = new \LimitRepetition;
$repetition->startdate = $flowStart;
$repetition->enddate = $flowEnd;
$repetition->amount = $limit->amount;
$repetition->limit()->associate($limit);
try {
$repetition->save();
} catch (\Illuminate\Database\QueryException $e) {
// do nothing
\Log::error($e->getMessage());
}
}
}
}
}
}
\Log::debug('Done checking the budget!');
}
}
public function subscribe(\Illuminate\Events\Dispatcher $events)
{
$events->listen('app.before', 'Firefly\Trigger\Limits\EloquentLimitTrigger@updateLimitRepetitions');
}
}
\Limit::observe(new EloquentLimitTrigger);

View File

@@ -14,13 +14,13 @@ use LaravelBook\Ardent\Ardent as Ardent;
* @property-read \AccountType $accountType
* @property-read \User $user
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @method static \Illuminate\Database\Query\Builder|\Account whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereAccountTypeId($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereActive($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereAccountTypeId($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereActive($value)
*/
class Account extends Ardent
{

View File

@@ -9,10 +9,10 @@
* @property \Carbon\Carbon $updated_at
* @property string $description
* @property-read \Illuminate\Database\Eloquent\Collection|\Account[] $accounts
* @method static \Illuminate\Database\Query\Builder|\AccountType whereId($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereDescription($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereId($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\AccountType whereDescription($value)
*/
class AccountType extends Eloquent
{

View File

@@ -3,29 +3,40 @@
/**
* Budget
*
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property integer $user_id
* @property string $class
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property integer $user_id
* @property string $class
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property-read \Illuminate\Database\Eloquent\Collection|\TransactionJournal[] $transactionjournals
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Budget whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereClass($value)
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Budget whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Budget whereClass($value)
* @property-read \Illuminate\Database\Eloquent\Collection|\Limit[] $limits
*/
class Budget extends Component {
class Budget extends Component
{
public static $factory
= [
'name' => 'string',
'user_id' => 'factory|User',
'class' => 'Budget'
];
protected $isSubclass = true;
public static $factory = [
'name' => 'string',
'user_id' => 'factory|User',
'class' => 'Budget'
];
public function limits()
{
return $this->hasMany('Limit', 'component_id');
}
public function transactionjournals() {
return $this->belongsToMany('TransactionJournal','component_transaction_journal','component_id');
}
}

View File

@@ -3,28 +3,30 @@
/**
* Category
*
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property integer $user_id
* @property string $class
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property integer $user_id
* @property string $class
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property-read \Illuminate\Database\Eloquent\Collection|\TransactionJournal[] $transactionjournals
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Category whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereClass($value)
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Category whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Category whereClass($value)
* @property-read \Limit $limits
*/
class Category extends Component
{
public static $factory
= [
'name' => 'string',
'user_id' => 'factory|User',
'class' => 'Category'
];
protected $isSubclass = true;
public static $factory = [
'name' => 'string',
'user_id' => 'factory|User',
'class' => 'Category'
];
}

View File

@@ -4,45 +4,47 @@
/**
* Component
*
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property integer $user_id
* @property string $class
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property integer $user_id
* @property string $class
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property-read \Illuminate\Database\Eloquent\Collection|\TransactionJournal[] $transactionjournals
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Component whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereClass($value)
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Component whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Component whereClass($value)
* @property-read \Limit $limits
*/
class Component extends Firefly\Database\SingleTableInheritanceEntity
{
public static $rules
= [
'user_id' => 'exists:users,id|required',
'name' => 'required|between:1,255',
'class' => 'required',
'user_id' => 'exists:users,id|required',
'name' => 'required|between:1,255',
'class' => 'required',
];
public static $factory
= [
'name' => 'string',
'user_id' => 'factory|User',
];
protected $table = 'components';
protected $subclassField = 'class';
public static $factory = [
'name' => 'string',
'user_id' => 'factory|User',
];
public function transactions()
{
return $this->belongsToMany('Transaction');
}
public function limits() {
public function limits()
{
return $this->belongsTo('Limit');
}

View File

@@ -2,6 +2,29 @@
use LaravelBook\Ardent\Ardent as Ardent;
/**
* Limit
*
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $component_id
* @property \Carbon\Carbon $startdate
* @property float $amount
* @property boolean $repeats
* @property string $repeat_freq
* @property-read \Component $component
* @property-read \Budget $budget
* @property-read \Illuminate\Database\Eloquent\Collection|\LimitRepetition[] $limitrepetitions
* @method static \Illuminate\Database\Query\Builder|\Limit whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereComponentId($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereStartdate($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereAmount($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereRepeats($value)
* @method static \Illuminate\Database\Query\Builder|\Limit whereRepeatFreq($value)
*/
class Limit extends Ardent
{
@@ -9,8 +32,9 @@ class Limit extends Ardent
= [
'component_id' => 'required|exists:components,id',
'startdate' => 'required|date',
'enddate' => 'required|date',
'amount' => 'numeric|required|min:0.01'
'amount' => 'numeric|required|min:0.01',
'repeats' => 'required|between:0,1',
'repeat_freq' => 'required|in:daily,weekly,monthly,quarterly,half-year,yearly'
];
@@ -24,7 +48,7 @@ class Limit extends Ardent
public function component()
{
return $this->belongsTo('Component');
return $this->belongsTo('Component','component_id');
}
public function budget()
@@ -32,6 +56,10 @@ class Limit extends Ardent
return $this->belongsTo('Budget', 'component_id');
}
public function limitrepetitions() {
return $this->hasMany('LimitRepetition');
}
public function getDates()
{
return ['created_at', 'updated_at', 'startdate', 'enddate'];

View File

@@ -0,0 +1,84 @@
<?php
use LaravelBook\Ardent\Ardent as Ardent;
/**
* LimitRepetition
*
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $limit_id
* @property \Carbon\Carbon $startdate
* @property \Carbon\Carbon $enddate
* @property float $amount
* @property-read \Limit $limit
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereId($value)
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereLimitId($value)
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereStartdate($value)
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereEnddate($value)
* @method static \Illuminate\Database\Query\Builder|\LimitRepetition whereAmount($value)
*/
class LimitRepetition extends Ardent
{
public static $rules
= [
'limit_id' => 'required|exists:limits,id',
'startdate' => 'required|date',
'enddate' => 'required|date',
'amount' => 'numeric|required|min:0.01',
];
public static $factory
= [
'limit_id' => 'factory|Limit',
'startdate' => 'date',
'enddate' => 'date',
'amount' => 'integer'
];
public function limit()
{
return $this->belongsTo('Limit');
}
public function getDates()
{
return ['created_at', 'updated_at', 'startdate', 'enddate'];
}
/**
* How much money is left in this?
*/
public function left()
{
$key = 'limit-rep-left-' . $this->id;
if (Cache::has($key)) {
return Cache::get($key);
}
$left = floatval($this->amount);
// budget:
$budget = $this->limit->budget;
/** @var \Firefly\Storage\Limit\EloquentLimitRepository $limits */
$limits = App::make('Firefly\Storage\Limit\EloquentLimitRepository');
$set = $limits->getTJByBudgetAndDateRange($budget, $this->startdate, $this->enddate);
foreach ($set as $journal) {
foreach ($journal->transactions as $t) {
if ($t->amount < 0) {
$left += floatval($t->amount);
}
}
}
Cache::forever($key, $left);
return $left;
}
}

View File

@@ -13,12 +13,12 @@ use LaravelBook\Ardent\Ardent;
* @property string $name
* @property string $data
* @property-read \User $user
* @method static \Illuminate\Database\Query\Builder|\Preference whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereData($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereName($value)
* @method static \Illuminate\Database\Query\Builder|\Preference whereData($value)
*/
class Preference extends Ardent
{

View File

@@ -18,13 +18,13 @@ use LaravelBook\Ardent\Ardent;
* @property-read \Illuminate\Database\Eloquent\Collection|\Component[] $components
* @property-read \Illuminate\Database\Eloquent\Collection|\Budget[] $budgets
* @property-read \Illuminate\Database\Eloquent\Collection|\Category[] $categories
* @method static \Illuminate\Database\Query\Builder|\Transaction whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereAccountId($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereTransactionJournalId($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereDescription($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereAmount($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereAccountId($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereTransactionJournalId($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereDescription($value)
* @method static \Illuminate\Database\Query\Builder|\Transaction whereAmount($value)
*/
class Transaction extends Ardent
{

View File

@@ -9,10 +9,10 @@
* @property \Carbon\Carbon $updated_at
* @property string $code
* @property-read \Illuminate\Database\Eloquent\Collection|\TransactionJournal[] $transactionJournals
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereCode($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionCurrency whereCode($value)
*/
class TransactionCurrency extends Eloquent
{

View File

@@ -32,6 +32,13 @@ use LaravelBook\Ardent\Ardent;
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDate($value)
* @method static \TransactionJournal after($date)
* @method static \TransactionJournal before($date)
* @property integer $user_id
* @property-read \User $user
* @property-read \Illuminate\Database\Eloquent\Collection|\
* 'Budget[] $budgets
* @property-read \Illuminate\Database\Eloquent\Collection|\
* 'Category[] $categories
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereUserId($value)
*/
class TransactionJournal extends Ardent
{

View File

@@ -9,10 +9,10 @@
* @property \Carbon\Carbon $updated_at
* @property string $type
* @property-read \Illuminate\Database\Eloquent\Collection|\TransactionJournal[] $transactionJournals
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereType($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionType whereType($value)
*/
class TransactionType extends Eloquent {
public function transactionJournals() {

View File

@@ -23,14 +23,15 @@ use LaravelBook\Ardent\Ardent;
* @property-read \Illuminate\Database\Eloquent\Collection|\Component[] $components
* @property-read \Illuminate\Database\Eloquent\Collection|\Budget[] $budgets
* @property-read \Illuminate\Database\Eloquent\Collection|\Category[] $categories
* @method static \Illuminate\Database\Query\Builder|\User whereId($value)
* @method static \Illuminate\Database\Query\Builder|\User whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\User whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\User whereEmail($value)
* @method static \Illuminate\Database\Query\Builder|\User wherePassword($value)
* @method static \Illuminate\Database\Query\Builder|\User whereReset($value)
* @method static \Illuminate\Database\Query\Builder|\User whereRememberToken($value)
* @method static \Illuminate\Database\Query\Builder|\User whereMigrated($value)
* @method static \Illuminate\Database\Query\Builder|\User whereId($value)
* @method static \Illuminate\Database\Query\Builder|\User whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\User whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\User whereEmail($value)
* @method static \Illuminate\Database\Query\Builder|\User wherePassword($value)
* @method static \Illuminate\Database\Query\Builder|\User whereReset($value)
* @method static \Illuminate\Database\Query\Builder|\User whereRememberToken($value)
* @method static \Illuminate\Database\Query\Builder|\User whereMigrated($value)
* @property-read \Illuminate\Database\Eloquent\Collection|\TransactionJournal[] $transactionjournals
*/
class User extends Ardent implements UserInterface, RemindableInterface
{

View File

@@ -26,8 +26,13 @@ Route::group(['before' => 'auth'], function () {
Route::get('/accounts/create', ['uses' => 'AccountController@create', 'as' => 'accounts.create']);
Route::get('/accounts/{account}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']);
// budget controller
Route::get('/bugets',['uses' => 'BudgetController@index','as' => 'budgets.index']);
// budget controller:
Route::get('/budgets',['uses' => 'BudgetController@index','as' => 'budgets.index']);
Route::get('/budget/create',['uses' => 'BudgetController@create', 'as' => 'budgets.create']);
Route::get('/budget/show/{id}',['uses' => 'BudgetController@show', 'as' => 'budgets.show']);
// limit controller:
Route::get('/budgets/limits/create/{id?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']);
// JSON controller:
Route::get('/json/beneficiaries', ['uses' => 'JsonController@beneficiaries', 'as' => 'json.beneficiaries']);
@@ -53,6 +58,9 @@ Route::group(['before' => 'csrf|auth'], function () {
// profile controller
Route::post('/profile/change-password', ['uses' => 'ProfileController@postChangePassword']);
// budget controller:
Route::post('/budget/store',['uses' => 'BudgetController@store', 'as' => 'budgets.store']);
// migration controller
Route::post('/migrate', ['uses' => 'MigrationController@postIndex']);
@@ -62,6 +70,9 @@ Route::group(['before' => 'csrf|auth'], function () {
// account controller:
Route::get('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']);
// limit controller:
Route::post('/limits/store', ['uses' => 'LimitController@store', 'as' => 'limits.store']);
// transaction controller:
Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])
->where(['what' => 'withdrawal|deposit|transfer']);
@@ -81,6 +92,7 @@ Route::group(['before' => 'guest'], function () {
// dev import route:
Route::get('/dev',['uses' => 'MigrationController@dev']);
Route::get('/limit',['uses' => 'MigrationController@limit']);
}
);

View File

@@ -0,0 +1,87 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Create a budget</small>
</h1>
<p class="text-info">
Firefly uses the <a href="http://en.wikipedia.org/wiki/Envelope_System" class="text-success">envelope system</a>. Every budget
is an envelope in which you put money every [period]. Expenses allocated to each budget are paid from this
(virtual) envelope.
</p>
<p class="text-info">
When the envelope is empty, you must stop spending on the budget. If the envelope still has some money left at the
end of the [period], congratulations! You have saved money!
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('budgets.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-3 control-label">Name</label>
<div class="col-sm-9">
<input type="text" name="name" class="form-control" id="name" value="{{Input::old('name')}}" placeholder="Name">
<span class="help-block">For example: groceries, bills</span>
</div>
</div>
</div>
<div class="col-lg-6 col-md-12 col-sm-6">
<h4>Optional fields</h4>
<div class="form-group">
<label for="amount" class="col-sm-3 control-label">Max. amount</label>
<div class="col-sm-9">
<input type="number" min="0.01" step="any" name="amount" class="form-control" id="amount" value="{{Input::old('amount')}}">
<span class="help-block">What's the most you're willing to spend in this budget? This amount is "put" in the virtual
envelope.</span>
</div>
</div>
<div class="form-group">
<label for="period" class="col-sm-3 control-label">Spending period</label>
<div class="col-sm-9">
{{Form::select('period',$periods,Input::old('period') ?: 'monthly',['class' => 'form-control'])}}
<span class="help-block">How long will the envelope last? A week, a month, or even longer?</span>
</div>
</div>
<div class="form-group">
<label for="period" class="col-sm-3 control-label">Repeat</label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="repeats">
Repeat
</label>
</div>
<span class="help-block">If you want, Firefly can automatically recreate the "envelope" and fill it again
when the timespan above has expired. Be careful with this option though. It makes it easier
to <a href="http://en.wikipedia.org/wiki/Personal_budget#Concepts">fall back to old habits</a>.
Instead, you should recreate the envelope yourself each [period].</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<input type="submit" name="submit" class="btn btn-info" value="Create new budget" />
<br /><br /><br /><br />
</div>
</div>
{{Form::close()}}
@stop
@section('scripts')
<script type="text/javascript" src="assets/javascript/moment.min.js"></script>
<script type="text/javascript" src="assets/javascript/limits.js"></script>
@stop

View File

@@ -0,0 +1,102 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Budgets and limits</small>
</h1>
<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.
</p>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<table class="table table-bordered table-striped">
<tr>
<th>Budget</th>
<th>Current envelope(s)</th>
<th>&nbsp;</th>
</tr>
@foreach($budgets as $budget)
<tr>
<td>
<a href="{{route('budgets.show',$budget->id)}}">{{{$budget->name}}}</a>
</td>
<td>
<div class="row">
<div class="col-sm-2">
<small>Envelope</small>
</div>
<div class="col-sm-2">
<small>Left</small>
</div>
</div>
@foreach($budget->limits as $limit)
@foreach($limit->limitrepetitions as $index => $rep)
<div class="row">
<div class="col-sm-2">
<span class="label label-primary">
<span class="glyphicon glyphicon-envelope"></span>
{{mf($rep->amount,false)}}</span>
</div>
<div class="col-sm-2">
@if($rep->left() < 0)
<span class="label label-danger">
<span class="glyphicon glyphicon-envelope"></span>
{{mf($rep->left(),false)}}</span>
@else
<span class="label label-success">
<span class="glyphicon glyphicon-envelope"></span>
{{mf($rep->left(),false)}}</span>
@endif
</div>
<div class="col-sm-3">
<small>
@if($limit->repeat_freq == 'monthly')
{{$rep->startdate->format('F Y')}}
@else
NO FORMAT
@endif
</small>
</div>
@if($limit->repeats == 1)
<div class="col-sm-2">
<span class="label label-warning">auto repeats</span>
</div>
@endif
<div class="col-sm-2 @if($limit->repeats == 0) col-sm-offset-2 @endif">
<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>
@endforeach
@endforeach
<p style="margin-top:5px;">
<a href="{{route('budgets.limits.create',$budget->id)}}" class="btn btn-default btn-xs"><span
class="glyphicon-plus-sign glyphicon"></span> Add another limit</a>
</p>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="#" class="btn btn-default"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
</div>
</td>
</tr>
@endforeach
</table>
</div>
</div>
@stop

View File

@@ -0,0 +1,4 @@
@extends('layouts.default')
@section('content')
@stop

View File

@@ -7,6 +7,7 @@
<small>What's playing?</small>
@endif
</h1>
@if($count > 0)
<form role="form" class="form-horizontal">
<div class="input-group">
@@ -23,6 +24,7 @@
<button class="btn btn-default btn-sm @if($r=='custom') btn-info @endif" type="submit" name="range" value="custom">Custom</button>
</div>
</form>
@endif
</div>
</div>
@@ -60,12 +62,35 @@
</div>
</div>
<!-- TRANSACTIONS -->
@if(count($transactions) > 0)
@foreach($transactions as $set)
<div class="row">
<?php $split = 12 / count($set); ?>
@foreach($set as $data)
<div class="col-lg-{{$split}} col-md-{{$split}}">
<h4>{{{$data[1]->name}}}</h4>
@include('transactions.journals',['transactions' => $data[0],'account' => $data[1]])
</div>
@endforeach
</div>
@endforeach
@endif
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div id="categories"></div>
</div>
</div>
@endif
@stop

View File

@@ -0,0 +1,103 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Set a limit to a budget</small>
</h1>
<p class="text-info">
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.
</p>
<p class="text-info">
Firefly gives you the opportunity to create such an envelope when you create a budget. However, you can
always add more of them.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('limits.store')])}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<h4>Mandatory fields</h4>
<div class="form-group">
{{ Form::label('budget_id', 'Budget', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
{{Form::select('budget_id',$budgets,Input::old('budget_id') ?: $budget_id, ['class' =>
'form-control'])}}
@if($errors->has('budget_id'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<span class="help-block">Select one of your existing budgets.</span>
@endif
</div>
</div>
<div class="form-group">
{{ Form::label('startdate', 'Start date', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
<input type="date" name="startdate" value="{{Input::old('startdate') ?: date('Y-m-d')}}"
class="form-control"/>
<span class="help-block">This date indicates when the envelope "starts". The date you select
here will correct itself to the nearest [period] you select below.</span>
</div>
</div>
<div class="form-group">
<label for="period" class="col-sm-3 control-label">Spending period</label>
<div class="col-sm-9">
{{Form::select('period',$periods,Input::old('period') ?: 'monthly',['class' => 'form-control'])}}
<span class="help-block">How long will the envelope last? A week, a month, or even longer?</span>
</div>
</div>
<div class="form-group">
<label for="period" class="col-sm-3 control-label">Repeat</label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="repeats" @if(intval(Input::old('repeats')) == 1) checked @endif>
Repeat
</label>
</div>
<span class="help-block">If you want, Firefly can automatically recreate the "envelope" and fill it again
when the timespan above has expired. Be careful with this option though. It makes it easier
to <a href="http://en.wikipedia.org/wiki/Personal_budget#Concepts">fall back to old habits</a>.
Instead, you should recreate the envelope yourself each [period].</span>
</div>
</div>
<div class="form-group">
{{ Form::label('amount', 'Amount', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
<input type="number" value="{{Input::old('amount')}}" name="amount" min="0.01" step="any" class="form-control"/>
<span class="help-block">Of course, there needs to be money in the envelope.</span>
</div>
</div>
<div class="form-group">
{{ Form::label('submit', '&nbsp;', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
<input type="submit" name="submit" value="Save new limit" class="btn btn-default"/>
</div>
</div>
</div>
</div>
{{Form::open()}}
@stop
@section('scripts')
<script type="text/javascript" src="assets/javascript/moment.min.js"></script>
<script type="text/javascript" src="assets/javascript/limits.js"></script>
@stop

View File

@@ -0,0 +1,28 @@
<?php
$r = Route::current()->getName();
?>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{route('index')}}">Firefly III</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li @if($r=='index')class="active"@endif><a href="{{route('index')}}">Home</a></li>
<li @if($r=='budgets.index')class="active"@endif><a href="{{route('budgets.index')}}">Budgets</a></li>
<li @if($r=='budgets.create')class="active"@endif><a href="{{route('budgets.create')}}"><span class="glyphicon glyphicon-plus"></span> Create budget</a></li>
<li @if($r=='budgets.limits.create')class="active"@endif><a href="{{route('budgets.limits.create')}}"><span class="glyphicon glyphicon-plus"></span> Set limit</a></li>
</ul>
@include('partials.menu.shared')
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>

View File

@@ -5,7 +5,6 @@
<h1>Firefly
<small>Preferences</small>
</h1>
</div>
</div>

View File

@@ -5,7 +5,7 @@
<th>Date</th>
<th>Amount</th>
</tr>
@foreach($account->transactionList as $journal)
@foreach($transactions as $journal)
<tr>
<td>