mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-01-23 23:13:18 -06:00
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:
parent
0bcda34738
commit
08cbd91dd9
97
app/controllers/BudgetController.php
Normal file
97
app/controllers/BudgetController.php
Normal 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');
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
49
app/controllers/LimitController.php
Normal file
49
app/controllers/LimitController.php
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 ' 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 ' 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
|
||||
*/
|
||||
|
@ -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')
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ App::before(
|
||||
if (Auth::check()) {
|
||||
\Firefly\Helper\Toolkit\Toolkit::getDateRange();
|
||||
}
|
||||
Event::fire('app.before');
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
||||
|
@ -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;
|
||||
|
@ -6,6 +6,9 @@ namespace Firefly\Storage\Budget;
|
||||
interface BudgetRepositoryInterface
|
||||
{
|
||||
public function getAsSelectList();
|
||||
public function get();
|
||||
|
||||
public function create($data);
|
||||
|
||||
public function find($id);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
||||
|
84
app/lib/Firefly/Storage/Limit/EloquentLimitRepository.php
Normal file
84
app/lib/Firefly/Storage/Limit/EloquentLimitRepository.php
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
12
app/lib/Firefly/Storage/Limit/LimitRepositoryInterface.php
Normal file
12
app/lib/Firefly/Storage/Limit/LimitRepositoryInterface.php
Normal 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);
|
||||
}
|
@ -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'
|
||||
|
178
app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php
Normal file
178
app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php
Normal 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);
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
@ -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'
|
||||
];
|
||||
}
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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'];
|
||||
|
84
app/models/LimitRepetition.php
Normal file
84
app/models/LimitRepetition.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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']);
|
||||
}
|
||||
);
|
||||
|
||||
|
87
app/views/budgets/create.blade.php
Normal file
87
app/views/budgets/create.blade.php
Normal 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
|
102
app/views/budgets/index.blade.php
Normal file
102
app/views/budgets/index.blade.php
Normal 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> </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
|
4
app/views/budgets/show.blade.php
Normal file
4
app/views/budgets/show.blade.php
Normal file
@ -0,0 +1,4 @@
|
||||
@extends('layouts.default')
|
||||
@section('content')
|
||||
|
||||
@stop
|
@ -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
|
||||
|
103
app/views/limits/create.blade.php
Normal file
103
app/views/limits/create.blade.php
Normal 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', ' ', ['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
|
28
app/views/partials/menu/budgets.blade.php
Normal file
28
app/views/partials/menu/budgets.blade.php
Normal 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>
|
@ -5,7 +5,6 @@
|
||||
<h1>Firefly
|
||||
<small>Preferences</small>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<th>Date</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
@foreach($account->transactionList as $journal)
|
||||
@foreach($transactions as $journal)
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
@ -94,4 +94,8 @@ require $framework . '/Illuminate/Foundation/start.php';
|
||||
|
|
||||
*/
|
||||
|
||||
// do something with events:
|
||||
Event::subscribe('Firefly\Trigger\Limits\EloquentLimitTrigger');
|
||||
|
||||
|
||||
return $app;
|
||||
|
104
public/assets/javascript/date.js
Normal file
104
public/assets/javascript/date.js
Normal file
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Version: 1.0 Alpha-1
|
||||
* Build Date: 13-Nov-2007
|
||||
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
|
||||
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
|
||||
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
|
||||
*/
|
||||
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
|
||||
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
||||
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
||||
return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))||(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var n=(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p in n){if(n[p]===offset){return p;}}
|
||||
return null;};Date.prototype.clone=function(){return new Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw new Error(this);}
|
||||
if(date instanceof Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var t=this.getTime();return t>=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
|
||||
var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);}
|
||||
if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);}
|
||||
if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);}
|
||||
if(x.hour||x.hours){this.addHours(x.hour||x.hours);}
|
||||
if(x.month||x.months){this.addMonths(x.month||x.months);}
|
||||
if(x.year||x.years){this.addYears(x.year||x.years);}
|
||||
if(x.day||x.days){this.addDays(x.day||x.days);}
|
||||
return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(value<min||value>max){throw new RangeError(value+" is not a valid value for "+name+".");}
|
||||
return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;}
|
||||
if(!x.second&&x.second!==0){x.second=-1;}
|
||||
if(!x.minute&&x.minute!==0){x.minute=-1;}
|
||||
if(!x.hour&&x.hour!==0){x.hour=-1;}
|
||||
if(!x.day&&x.day!==0){x.day=-1;}
|
||||
if(!x.month&&x.month!==0){x.month=-1;}
|
||||
if(!x.year&&x.year!==0){x.year=-1;}
|
||||
if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());}
|
||||
if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());}
|
||||
if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());}
|
||||
if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());}
|
||||
if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());}
|
||||
if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());}
|
||||
if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());}
|
||||
if(x.timezone){this.setTimezone(x.timezone);}
|
||||
if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);}
|
||||
return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;}
|
||||
var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}}
|
||||
return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();};
|
||||
Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
|
||||
return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
|
||||
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
|
||||
return this.moveToMonth(n,this._orient);};};for(var j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
|
||||
var ef=function(j){return function(){if(j.substring(j.length-1)!="s"){j+="s";}
|
||||
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};
|
||||
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
|
||||
break;}
|
||||
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
|
||||
rx.push(r[0]);s=r[1];}
|
||||
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
|
||||
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
|
||||
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
|
||||
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
|
||||
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
||||
try{r=(px[i].call(this,s));}catch(e){r=null;}
|
||||
if(r){return r;}}
|
||||
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
||||
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
|
||||
rx.push(r[0]);s=r[1];}
|
||||
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
|
||||
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
|
||||
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
|
||||
s=q[1];}
|
||||
if(!r){throw new $P.Exception(s);}
|
||||
if(q){throw new $P.Exception(q[1]);}
|
||||
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
|
||||
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
|
||||
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
|
||||
if(!last&&q[1].length===0){last=true;}
|
||||
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
|
||||
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
|
||||
if(rx[1].length<best[1].length){best=rx;}
|
||||
if(best[1].length===0){break;}}
|
||||
if(best[0].length===0){return best;}
|
||||
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
|
||||
best[1]=q[1];}
|
||||
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
|
||||
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
|
||||
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
|
||||
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
|
||||
return rx;};Date.Grammar={};Date.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];var now=new Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
|
||||
this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
|
||||
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
|
||||
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
|
||||
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
|
||||
if(this.now){return new Date();}
|
||||
var today=Date.today();var method=null;var expression=!!(this.days!=null||this.orient||this.operator);if(expression){var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
|
||||
if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
|
||||
if(!this.unit){this.unit="day";}
|
||||
if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
|
||||
if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
|
||||
this[this.unit+"s"]=this.value*orient;}
|
||||
return today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
|
||||
if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
|
||||
if(this.month&&!this.day){this.day=1;}
|
||||
return today.set(this);}}};var _=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
|
||||
fn=_C[keys]=_.any.apply(null,px);}
|
||||
return fn;};g.ctoken2=function(key){return _.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
|
||||
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
|
||||
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
|
||||
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
|
||||
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
|
||||
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};
|
@ -70,7 +70,7 @@ $(function () {
|
||||
y: e.pageY
|
||||
},
|
||||
objectType: 'ajax',
|
||||
headingText: this.series.name,
|
||||
headingText: '<a href="#">' + this.series.name + '</a>',
|
||||
width: 250
|
||||
}
|
||||
)
|
||||
@ -146,4 +146,8 @@ $(function () {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get chart data for budget charts.
|
||||
*/
|
||||
|
||||
});
|
39
public/assets/javascript/limits.js
Normal file
39
public/assets/javascript/limits.js
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
console.log(moment().startOf('month').format('YYYY-MM-DD'));
|
||||
|
||||
$(function () {
|
||||
|
||||
$('#this-week').click(function (e) {
|
||||
$('input[name="startdate"]').val(moment().startOf('isoWeek').format('YYYY-MM-DD'));
|
||||
$('input[name="enddate"]').val(moment().endOf('isoWeek').format('YYYY-MM-DD'));
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#this-month').click(function (e) {
|
||||
$('input[name="startdate"]').val(moment().startOf('month').format('YYYY-MM-DD'));
|
||||
$('input[name="enddate"]').val(moment().endOf('month').format('YYYY-MM-DD'));
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#this-quarter').click(function (e) {
|
||||
$('input[name="startdate"]').val(moment().startOf('quarter').format('YYYY-MM-DD'));
|
||||
$('input[name="enddate"]').val(moment().endOf('quarter').format('YYYY-MM-DD'));
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#this-year').click(function (e) {
|
||||
$('input[name="startdate"]').val(moment().startOf('year').format('YYYY-MM-DD'));
|
||||
$('input[name="enddate"]').val(moment().endOf('year').format('YYYY-MM-DD'));
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
function formatAsStr(dt) {
|
||||
return dt.getFullYear() + '-'
|
||||
+ ('0' + (dt.getMonth() + 1)).slice(-2) + '-' +
|
||||
('0' + dt.getDate()).slice(-2);
|
||||
}
|
||||
|
6
public/assets/javascript/moment.min.js
vendored
Normal file
6
public/assets/javascript/moment.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user