Finally implemented repeated expenses properly. [skip ci]

This commit is contained in:
James Cole 2014-12-30 15:17:01 +01:00
parent c0c37eec7b
commit b451e207e2
17 changed files with 648 additions and 474 deletions

View File

@ -32,6 +32,11 @@ class HelpController extends BaseController
} catch (ErrorException $e) {
$content = '<p>There is no help for this route.</p>';
}
if (strlen($content) == 0) {
$content = '<p>There is no help for this route.</p>';
}
\Log::debug('Found help for ' . $route);
\Log::debug('Help text length is ' . strlen($content));
$helpText = \Michelf\Markdown::defaultTransform($content);
$helpTitle = $route;

View File

@ -35,11 +35,68 @@ class RepeatedExpenseController extends BaseController
$accounts = FFForm::makeSelectList($acct->getAssetAccounts());
return View::make('repeatedexpense.create', compact('accounts', 'periods'))->with('subTitle', 'Create new repeated expense')->with(
return View::make('repeatedExpense.create', compact('accounts', 'periods'))->with('subTitle', 'Create new repeated expense')->with(
'subTitleIcon', 'fa-plus'
);
}
/**
* @param PiggyBank $repeatedExpense
*
* @return $this
*/
public function delete(PiggyBank $repeatedExpense)
{
$subTitle = 'Delete "' . e($repeatedExpense->name) . '"';
return View::make('repeatedExpense.delete', compact('repeatedExpense', 'subTitle'));
}
/**
* @param PiggyBank $repeatedExpense
*
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(PiggyBank $repeatedExpense)
{
Session::flash('success', 'Repeated expense "' . e($repeatedExpense->name) . '" deleted.');
$this->_repository->destroy($repeatedExpense);
return Redirect::route('repeated.index');
}
/**
* @param PiggyBank $repeatedExpense
*
* @return $this
*/
public function edit(PiggyBank $repeatedExpense)
{
/** @var \FireflyIII\Database\Account\Account $acct */
$acct = App::make('FireflyIII\Database\Account\Account');
$periods = Config::get('firefly.piggy_bank_periods');
$accounts = FFForm::makeSelectList($acct->getAssetAccounts());
$subTitle = 'Edit repeated expense "' . e($repeatedExpense->name) . '"';
$subTitleIcon = 'fa-pencil';
/*
* Flash some data to fill the form.
*/
$preFilled = ['name' => $repeatedExpense->name,
'account_id' => $repeatedExpense->account_id,
'targetamount' => $repeatedExpense->targetamount,
'targetdate' => $repeatedExpense->targetdate->format('Y-m-d'),
'reminder' => $repeatedExpense->reminder,
'remind_me' => intval($repeatedExpense->remind_me) == 1 || !is_null($repeatedExpense->reminder) ? true : false
];
Session::flash('preFilled', $preFilled);
return View::make('repeatedExpense.edit', compact('subTitle', 'subTitleIcon', 'repeatedExpense', 'accounts', 'periods', 'preFilled'));
}
/**
* @return \Illuminate\View\View
*/
@ -48,50 +105,51 @@ class RepeatedExpenseController extends BaseController
$subTitle = 'Overview';
/** @var \FireflyIII\Database\PiggyBank\RepeatedExpense $repository */
$repository = App::make('FireflyIII\Database\PiggyBank\RepeatedExpense');
$expenses = $repository->get();
$expenses = $this->_repository->get();
$expenses->each(
function (PiggyBank $piggyBank) use ($repository) {
function (PiggyBank $piggyBank) {
$piggyBank->currentRelevantRep();
}
);
return View::make('repeatedexpense.index', compact('expenses', 'subTitle'));
return View::make('repeatedExpense.index', compact('expenses', 'subTitle'));
}
/**
* @param PiggyBank $piggyBank
* @param PiggyBank $repeatedExpense
*
* @return \Illuminate\View\View
*/
public function show(PiggyBank $piggyBank)
public function show(PiggyBank $repeatedExpense)
{
$subTitle = $piggyBank->name;
$subTitle = $repeatedExpense->name;
$today = Carbon::now();
/** @var \FireflyIII\Database\PiggyBank\RepeatedExpense $repository */
$repository = App::make('FireflyIII\Database\PiggyBank\RepeatedExpense');
$repetitions = $piggyBank->piggyBankRepetitions()->get();
$repetitions = $repeatedExpense->piggyBankRepetitions()->get();
$repetitions->each(
function (PiggyBankRepetition $repetition) use ($repository) {
$repetition->bars = $repository->calculateParts($repetition);
function (PiggyBankRepetition $repetition) {
$repetition->bars = $this->_repository->calculateParts($repetition);
}
);
return View::make('repeatedexpense.show', compact('repetitions', 'piggyBank', 'today', 'subTitle'));
return View::make('repeatedExpense.show', compact('repetitions', 'repeatedExpense', 'today', 'subTitle'));
}
/**
* @return $this
* @throws FireflyException
*
*/
public function store()
{
$data = Input::except('_token');
$data['repeats'] = 1;
$data = Input::all();
$data['repeats'] = 1;
$data['user_id'] = Auth::user()->id;
$targetDate = new Carbon($data['targetdate']);
$startDate = \DateKit::subtractPeriod($targetDate, $data['rep_length']);
$data['startdate'] = $startDate->format('Y-m-d');
$data['targetdate'] = $targetDate->format('Y-m-d');
$data['reminder_skip'] = 0;
$data['remind_me'] = isset($data['remind_me']) ? 1 : 0;
$data['order'] = 0;
// always validate:
$messages = $this->_repository->validate($data);
@ -101,25 +159,70 @@ class RepeatedExpenseController extends BaseController
Session::flash('successes', $messages['successes']);
Session::flash('errors', $messages['errors']);
if ($messages['errors']->count() > 0) {
Session::flash('error', 'Could not validate repeated expense: ' . $messages['errors']->first());
Session::flash('error', 'Could not store repeated expense: ' . $messages['errors']->first());
}
// return to create screen:
if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) {
return Redirect::route('repeated.create')->withInput();
}
// store:
$this->_repository->store($data);
Session::flash('success', 'Budget "' . e($data['name']) . '" stored.');
$piggyBank = $this->_repository->store($data);
Event::fire('piggy_bank.store', [$piggyBank]); // new and used.
Session::flash('success', 'Piggy bank "' . e($data['name']) . '" stored.');
if ($data['post_submit_action'] == 'store') {
return Redirect::route('repeated.index');
}
// create another.
if ($data['post_submit_action'] == 'create_another') {
return Redirect::route('repeated.create')->withInput();
return Redirect::route('repeated.create')->withInput();
}
/**
* @param PiggyBank $repeatedExpense
*
* @return $this
* @throws FireflyException
*/
public function update(PiggyBank $repeatedExpense)
{
$data = Input::except('_token');
$data['rep_every'] = 0;
$data['reminder_skip'] = 0;
$data['order'] = 0;
$data['repeats'] = 1;
$data['remind_me'] = isset($data['remind_me']) ? 1 : 0;
$data['user_id'] = Auth::user()->id;
// always validate:
$messages = $this->_repository->validate($data);
// flash messages:
Session::flash('warnings', $messages['warnings']);
Session::flash('successes', $messages['successes']);
Session::flash('errors', $messages['errors']);
if ($messages['errors']->count() > 0) {
Session::flash('error', 'Could not update repeated expense: ' . $messages['errors']->first());
}
return Redirect::route('repeated.index');
// return to update screen:
if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) {
return Redirect::route('repeated.edit', $repeatedExpense->id)->withInput();
}
// update
$this->_repository->update($repeatedExpense, $data);
Session::flash('success', 'Repeated expense "' . e($data['name']) . '" updated.');
// go back to list
if ($data['post_submit_action'] == 'update') {
return Redirect::route('repeated.index');
}
// go back to update screen.
return Redirect::route('repeated.edit', $repeatedExpense->id)->withInput(['post_submit_action' => 'return_to_edit']);
}
}

View File

@ -4,223 +4,18 @@ namespace FireflyIII\Database\PiggyBank;
use Carbon\Carbon;
use FireflyIII\Database\CommonDatabaseCalls;
use FireflyIII\Database\CUD;
use FireflyIII\Database\SwitchUser;
use FireflyIII\Exception\FireflyException;
use FireflyIII\Exception\NotImplementedException;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
/**
* Class PiggyBank
*
* @package FireflyIII\Database
*/
class PiggyBank implements CUD, CommonDatabaseCalls, PiggyBankInterface
class PiggyBank extends PiggyBankShared implements CUD, CommonDatabaseCalls, PiggyBankInterface
{
use SwitchUser;
/**
*
*/
public function __construct()
{
$this->setUser(\Auth::user());
}
/**
* @param Eloquent $model
*
* @return bool
*/
public function destroy(Eloquent $model)
{
$model->delete();
}
/**
* @param array $data
*
* @return \Eloquent
* @throws FireflyException
*/
public function store(array $data)
{
if (!isset($data['remind_me']) || (isset($data['remind_me']) && $data['remind_me'] == 0)) {
$data['reminder'] = null;
}
$piggyBank = new \PiggyBank($data);
$piggyBank->save();
return $piggyBank;
}
/**
* @param Eloquent $model
* @param array $data
*
* @return bool
*/
public function update(Eloquent $model, array $data)
{
/** @var \PiggyBank $model */
$model->name = $data['name'];
$model->account_id = intval($data['account_id']);
$model->targetamount = floatval($data['targetamount']);
$model->targetdate = isset($data['targetdate']) && $data['targetdate'] != '' ? $data['targetdate'] : null;
$model->rep_every = intval($data['rep_every']);
$model->reminder_skip = intval($data['reminder_skip']);
$model->order = intval($data['order']);
$model->remind_me = intval($data['remind_me']);
$model->reminder = isset($data['reminder']) ? $data['reminder'] : 'month';
if ($model->remind_me == 0) {
$model->reminder = null;
}
$model->save();
return true;
}
/**
* Validates an array. Returns an array containing MessageBags
* errors/warnings/successes.
*
* Ignore PHPMD rules because Laravel 5.0 will make this method superfluous anyway.
*
* @param array $model
*
* @return array
*/
public function validate(array $model)
{
$warnings = new MessageBag;
$successes = new MessageBag;
$errors = new MessageBag;
/*
* Name validation:
*/
if (!isset($model['name'])) {
$errors->add('name', 'Name is mandatory');
}
if (isset($model['name']) && strlen($model['name']) == 0) {
$errors->add('name', 'Name is too short');
}
if (isset($model['name']) && strlen($model['name']) > 100) {
$errors->add('name', 'Name is too long');
}
if (intval($model['account_id']) == 0) {
$errors->add('account_id', 'Account is mandatory');
}
if ($model['targetdate'] == '' && isset($model['remind_me']) && intval($model['remind_me']) == 1) {
$errors->add('targetdate', 'Target date is mandatory when setting reminders.');
}
if ($model['targetdate'] != '') {
try {
new Carbon($model['targetdate']);
} catch (\Exception $e) {
$errors->add('targetdate', 'Invalid date.');
}
}
if (floatval($model['targetamount']) < 0.01) {
$errors->add('targetamount', 'Amount should be above 0.01.');
}
if (!in_array(ucfirst($model['reminder']), \Config::get('firefly.piggy_bank_periods'))) {
$errors->add('reminder', 'Invalid reminder period (' . $model['reminder'] . ')');
}
// check period.
if (!$errors->has('reminder') && !$errors->has('targetdate') && isset($model['remind_me']) && intval($model['remind_me']) == 1) {
$today = new Carbon;
$target = new Carbon($model['targetdate']);
switch ($model['reminder']) {
case 'week':
$today->addWeek();
break;
case 'month':
$today->addMonth();
break;
case 'year':
$today->addYear();
break;
}
if ($today > $target) {
$errors->add('reminder', 'Target date is too close to today to set reminders.');
}
}
$validator = \Validator::make($model, \PiggyBank::$rules);
if ($validator->invalid()) {
$errors->merge($errors);
}
// add ok messages.
$list = ['name', 'account_id', 'targetamount', 'targetdate', 'remind_me', 'reminder'];
foreach ($list as $entry) {
if (!$errors->has($entry) && !$warnings->has($entry)) {
$successes->add($entry, 'OK');
}
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
}
/**
* Returns an object with id $id.
*
* @param int $objectId
*
* @return \Eloquent
*/
public function find($objectId)
{
return \PiggyBank::
leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('piggy_banks.id', '=', $objectId)->where(
'accounts.user_id', $this->getUser()->id
)
->first(['piggy_banks.*']);
}
/**
* Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc.
*
* @param $what
*
* @return \AccountType|null
* @throws NotImplementedException
*/
public function findByWhat($what)
{
// TODO: Implement findByWhat() method.
throw new NotImplementedException;
}
/**
* Returns all objects.
*
* @return Collection
*/
public function get()
{
return $this->getUser()->piggyBanks()->where('repeats', 0)->orderBy('name')->get();
}
/**
* @param array $ids
*
* @return Collection
* @throws NotImplementedException
*/
public function getByIds(array $ids)
{
// TODO: Implement getByIds() method.
throw new NotImplementedException;
}
/**
* @param \PiggyBank $piggyBank
* @param Carbon $date
@ -257,21 +52,12 @@ class PiggyBank implements CUD, CommonDatabaseCalls, PiggyBankInterface
}
/**
* @param \Account $account
* Returns all objects.
*
* @return float
* @return Collection
*/
public function leftOnAccount(\Account $account)
public function get()
{
\Log::debug('Now in leftOnAccount() for account #'.$account->id.' ('.$account->name.')');
$balance = \Steam::balance($account);
\Log::debug('Steam says: ' . $balance);
/** @var \PiggyBank $p */
foreach ($account->piggyBanks()->get() as $p) {
$balance -= $p->currentRelevantRep()->currentamount;
}
return $balance;
return $this->getUser()->piggyBanks()->where('repeats', 0)->orderBy('name')->get();
}
}

View File

@ -0,0 +1,220 @@
<?php
namespace FireflyIII\Database\PiggyBank;
use Carbon\Carbon;
use FireflyIII\Database\SwitchUser;
use FireflyIII\Exception\NotImplementedException;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
/**
* Class PiggyBankShared
*
* @package FireflyIII\Database\PiggyBank
*/
class PiggyBankShared
{
use SwitchUser;
/**
*
*/
public function __construct()
{
$this->setUser(\Auth::user());
}
/**
* @param Eloquent $model
*
* @return bool
*/
public function destroy(Eloquent $model)
{
$model->delete();
}
/**
* Returns an object with id $id.
*
* @param int $objectId
*
* @return \Eloquent
*/
public function find($objectId)
{
return \PiggyBank::
leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('piggy_banks.id', '=', $objectId)->where(
'accounts.user_id', $this->getUser()->id
)
->first(['piggy_banks.*']);
}
/**
* Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc.
*
* @param $what
*
* @return \AccountType|null
* @throws NotImplementedException
*/
public function findByWhat($what)
{
// TODO: Implement findByWhat() method.
throw new NotImplementedException;
}
/**
* @param array $ids
*
* @return Collection
* @throws NotImplementedException
*/
public function getByIds(array $ids)
{
return \PiggyBank::
leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->whereIn('piggy_banks.id', [$ids])->where(
'accounts.user_id', $this->getUser()->id
)
->first(['piggy_banks.*']);
}
/**
* @param \Account $account
*
* @return float
*/
public function leftOnAccount(\Account $account)
{
\Log::debug('Now in leftOnAccount() for account #' . $account->id . ' (' . $account->name . ')');
$balance = \Steam::balance($account);
\Log::debug('Steam says: ' . $balance);
/** @var \PiggyBank $p */
foreach ($account->piggyBanks()->get() as $p) {
$balance -= $p->currentRelevantRep()->currentamount;
}
return $balance;
}
/**
* @param array $data
*
* @return \Eloquent
* @throws FireflyException
*/
public function store(array $data)
{
if (!isset($data['remind_me']) || (isset($data['remind_me']) && $data['remind_me'] == 0)) {
$data['reminder'] = null;
}
$piggyBank = new \PiggyBank($data);
$piggyBank->save();
return $piggyBank;
}
/**
* @param Eloquent $model
* @param array $data
*
* @return bool
*/
public function update(Eloquent $model, array $data)
{
/** @var \PiggyBank $model */
$model->name = $data['name'];
$model->account_id = intval($data['account_id']);
$model->targetamount = floatval($data['targetamount']);
$model->targetdate = isset($data['targetdate']) && $data['targetdate'] != '' ? $data['targetdate'] : null;
$model->rep_every = intval($data['rep_every']);
$model->reminder_skip = intval($data['reminder_skip']);
$model->order = intval($data['order']);
$model->remind_me = intval($data['remind_me']);
$model->reminder = isset($data['reminder']) ? $data['reminder'] : 'month';
if ($model->remind_me == 0) {
$model->reminder = null;
}
$model->save();
return true;
}
/**
* Validates an array. Returns an array containing MessageBags
* errors/warnings/successes.
*
* Ignore PHPMD rules because Laravel 5.0 will make this method superfluous anyway.
*
* @param array $model
*
* @return array
*/
public function validate(array $model)
{
$warnings = new MessageBag;
$successes = new MessageBag;
$model = new \PiggyBank($model);
$model->isValid();
$errors = $model->getErrors();
// add ok messages.
$list = ['name', 'account_id', 'targetamount', 'targetdate', 'remind_me', 'reminder'];
foreach ($list as $entry) {
if (!$errors->has($entry) && !$warnings->has($entry)) {
$successes->add($entry, 'OK');
}
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
exit;
if (!in_array(ucfirst($model['reminder']), \Config::get('firefly.piggy_bank_periods'))) {
$errors->add('reminder', 'Invalid reminder period (' . $model['reminder'] . ')');
}
// check period.
if (!$errors->has('reminder') && !$errors->has('targetdate') && isset($model['remind_me']) && intval($model['remind_me']) == 1) {
$today = new Carbon;
$target = new Carbon($model['targetdate']);
switch ($model['reminder']) {
case 'week':
$today->addWeek();
break;
case 'month':
$today->addMonth();
break;
case 'year':
$today->addYear();
break;
}
if ($today > $target) {
$errors->add('reminder', 'Target date is too close to today to set reminders.');
}
}
$validator = \Validator::make($model, \PiggyBank::$rules);
if ($validator->invalid()) {
$errors->merge($errors);
}
// add ok messages.
$list = ['name', 'account_id', 'targetamount', 'targetdate', 'remind_me', 'reminder'];
foreach ($list as $entry) {
if (!$errors->has($entry) && !$warnings->has($entry)) {
$successes->add($entry, 'OK');
}
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
}
}

View File

@ -3,7 +3,6 @@
namespace FireflyIII\Database\PiggyBank;
use Carbon\Carbon;
use FireflyIII\Collection\PiggyBankPart;
use FireflyIII\Database\CommonDatabaseCalls;
use FireflyIII\Database\CUD;
@ -11,24 +10,14 @@ use FireflyIII\Database\SwitchUser;
use FireflyIII\Exception\NotImplementedException;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
/**
* Class RepeatedExpense
*
* @package FireflyIII\Database
*/
class RepeatedExpense implements CUD, CommonDatabaseCalls, PiggyBankInterface
class RepeatedExpense extends PiggyBankShared implements CUD, CommonDatabaseCalls, PiggyBankInterface
{
use SwitchUser;
/**
*
*/
public function __construct()
{
$this->setUser(\Auth::user());
}
/**
* Based on the piggy bank, the reminder-setting and
@ -97,17 +86,6 @@ class RepeatedExpense implements CUD, CommonDatabaseCalls, PiggyBankInterface
return $part;
}
/**
* @param Eloquent $model
*
* @return bool
* @throws NotImplementedException
*/
public function destroy(Eloquent $model)
{
// TODO: Implement destroy() method.
throw new NotImplementedException;
}
/**
* @param array $data
@ -134,144 +112,6 @@ class RepeatedExpense implements CUD, CommonDatabaseCalls, PiggyBankInterface
return $repeated;
}
/**
* @param Eloquent $model
* @param array $data
*
* @return bool
* @throws NotImplementedException
*/
public function update(Eloquent $model, array $data)
{
// TODO: Implement update() method.
throw new NotImplementedException;
}
/**
* Validates an array. Returns an array containing MessageBags
* errors/warnings/successes.
*
*
* ignored because this method will be gone soon.
*
* @param array $model
*
* @return array
*/
public function validate(array $model)
{
$warnings = new MessageBag;
$successes = new MessageBag;
$errors = new MessageBag;
/*
* Name validation:
*/
if (!isset($model['name'])) {
$errors->add('name', 'Name is mandatory');
}
if (isset($model['name']) && strlen($model['name']) == 0) {
$errors->add('name', 'Name is too short');
}
if (isset($model['name']) && strlen($model['name']) > 100) {
$errors->add('name', 'Name is too long');
}
if (intval($model['account_id']) == 0) {
$errors->add('account_id', 'Account is mandatory');
}
if ($model['targetdate'] == '' && isset($model['remind_me']) && intval($model['remind_me']) == 1) {
$errors->add('targetdate', 'Target date is mandatory when setting reminders.');
}
if ($model['targetdate'] != '') {
try {
new Carbon($model['targetdate']);
} catch (\Exception $e) {
$errors->add('targetdate', 'Invalid date.');
}
$diff = Carbon::now()->diff(new Carbon($model['targetdate']));
if ($diff->days > 365) {
$errors->add('targetdate', 'First target date should a a year or less from now.');
}
} else {
$errors->add('targetdate', 'Invalid target date.');
}
if (floatval($model['targetamount']) < 0.01) {
$errors->add('targetamount', 'Amount should be above 0.01.');
}
if (!in_array(ucfirst($model['reminder']), \Config::get('firefly.piggy_bank_periods'))) {
$errors->add('reminder', 'Invalid reminder period (' . $model['reminder'] . ')');
}
if (!in_array(ucfirst($model['rep_length']), \Config::get('firefly.piggy_bank_periods'))) {
$errors->add('rep_length', 'Invalid repeat period (' . $model['rep_length'] . ')');
}
// check period.
if (!$errors->has('reminder') && !$errors->has('targetdate') && isset($model['remind_me']) && intval($model['remind_me']) == 1) {
$today = new Carbon;
$target = new Carbon($model['targetdate']);
switch ($model['reminder']) {
case 'week':
$today->addWeek();
break;
case 'month':
$today->addMonth();
break;
case 'year':
$today->addYear();
break;
}
if ($today > $target) {
$errors->add('reminder', 'Target date is too close to today to set reminders.');
}
}
$validator = \Validator::make($model, \PiggyBank::$rules);
if ($validator->invalid()) {
$errors->merge($errors);
}
// add ok messages.
$list = ['name', 'account_id', 'rep_every', 'rep_times', 'rep_length', 'targetamount', 'targetdate', 'remind_me', 'reminder'];
foreach ($list as $entry) {
if (!$errors->has($entry) && !$warnings->has($entry)) {
$successes->add($entry, 'OK');
}
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
}
/**
* Returns an object with id $id.
*
* @param int $objectId
*
* @return \Eloquent
* @throws NotImplementedException
*/
public function find($objectId)
{
// TODO: Implement find() method.
throw new NotImplementedException;
}
/**
* Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc.
*
* @param $what
*
* @return \AccountType|null
* @throws NotImplementedException
*/
public function findByWhat($what)
{
// TODO: Implement findByWhat() method.
throw new NotImplementedException;
}
/**
* Returns all objects.
*
@ -282,27 +122,4 @@ class RepeatedExpense implements CUD, CommonDatabaseCalls, PiggyBankInterface
return $this->getUser()->piggyBanks()->where('repeats', 1)->get();
}
/**
* @param array $ids
*
* @return Collection
* @throws NotImplementedException
*/
public function getByIds(array $ids)
{
// TODO: Implement getByIds() method.
throw new NotImplementedException;
}
/**
* @param \Account $account
*
* @return float
* @throws NotImplementedException
*/
public function leftOnAccount(\Account $account)
{
// TODO: Implement leftOnAccount() method.
throw new NotImplementedException;
}
}

View File

@ -241,12 +241,15 @@ class Date
default:
throw new FireflyException('Cannot do subtractPeriod for $repeat_freq ' . $repeatFreq);
break;
case 'day':
case 'daily':
$date->subDays($subtract);
break;
case 'week':
case 'weekly':
$date->subWeeks($subtract);
break;
case 'month':
case 'monthly':
$date->subMonths($subtract);
break;

View File

@ -9,10 +9,10 @@ use Watson\Validating\ValidatingTrait;
class PiggyBank extends Eloquent
{
use ValidatingTrait;
public static $rules
protected $rules
= ['account_id' => 'required|exists:accounts,id', // link to Account
'name' => 'required|between:1,255', // name
'targetamount' => 'required|min:0', // amount you want to save
'targetamount' => 'required|min:0.01|numeric', // amount you want to save
'startdate' => 'date', // when you started
'targetdate' => 'date', // when its due
'repeats' => 'required|boolean', // does it repeat?

View File

@ -119,7 +119,7 @@ Route::bind(
);
Route::bind(
'piggy_bank', function ($value, $route) {
'piggyBank', function ($value, $route) {
if (Auth::check()) {
return PiggyBank::
where('piggy_banks.id', $value)
@ -132,6 +132,20 @@ Route::bind(
}
);
Route::bind(
'repeatedExpense', function ($value, $route) {
if (Auth::check()) {
return PiggyBank::
where('piggy_banks.id', $value)
->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
->where('accounts.user_id', Auth::user()->id)
->where('repeats', 1)->first(['piggy_banks.*']);
}
return null;
}
);
Route::bind(
'repeated', function ($value, $route) {
if (Auth::check()) {
@ -198,7 +212,7 @@ Route::group(
Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']);
Route::get('/chart/bills/{bill}', ['uses' => 'GoogleChartController@billOverview']);
Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'GoogleChartController@budgetLimitSpending']);
Route::get('/chart/piggy_history/{piggy_bank}', ['uses' => 'GoogleChartController@piggyBankHistory']);
Route::get('/chart/piggy_history/{piggyBank}', ['uses' => 'GoogleChartController@piggyBankHistory']);
// google chart for components (categories + budgets combined)
Route::get('/chart/budget/{budget}/spending/{year}', ['uses' => 'GoogleChartController@budgetsAndSpending']);
@ -219,13 +233,13 @@ Route::group(
// piggy bank controller
Route::get('/piggy_banks', ['uses' => 'PiggyBankController@index', 'as' => 'piggy_banks.index']);
Route::get('/piggy_banks/add/{piggy_bank}', ['uses' => 'PiggyBankController@add']); # add money
Route::get('/piggy_banks/remove/{piggy_bank}', ['uses' => 'PiggyBankController@remove']); #remove money
Route::get('/piggy_banks/add/{piggyBank}', ['uses' => 'PiggyBankController@add']); # add money
Route::get('/piggy_banks/remove/{piggyBank}', ['uses' => 'PiggyBankController@remove']); #remove money
Route::get('/piggy_banks/create', ['uses' => 'PiggyBankController@create', 'as' => 'piggy_banks.create']);
Route::get('/piggy_banks/edit/{piggy_bank}', ['uses' => 'PiggyBankController@edit', 'as' => 'piggy_banks.edit']);
Route::get('/piggy_banks/delete/{piggy_bank}', ['uses' => 'PiggyBankController@delete', 'as' => 'piggy_banks.delete']);
Route::get('/piggy_banks/show/{piggy_bank}', ['uses' => 'PiggyBankController@show', 'as' => 'piggy_banks.show']);
Route::get('/piggy_banks/edit/{piggyBank}', ['uses' => 'PiggyBankController@edit', 'as' => 'piggy_banks.edit']);
Route::get('/piggy_banks/delete/{piggyBank}', ['uses' => 'PiggyBankController@delete', 'as' => 'piggy_banks.delete']);
Route::get('/piggy_banks/show/{piggyBank}', ['uses' => 'PiggyBankController@show', 'as' => 'piggy_banks.show']);
// preferences controller
Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']);
@ -245,7 +259,9 @@ Route::group(
// repeated expenses controller:
Route::get('/repeatedexpenses', ['uses' => 'RepeatedExpenseController@index', 'as' => 'repeated.index']);
Route::get('/repeatedexpenses/create', ['uses' => 'RepeatedExpenseController@create', 'as' => 'repeated.create']);
Route::get('/repeatedexpenses/show/{repeated}', ['uses' => 'RepeatedExpenseController@show', 'as' => 'repeated.show']);
Route::get('/repeatedexpenses/edit/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@edit', 'as' => 'repeated.edit']);
Route::get('/repeatedexpenses/delete/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@delete', 'as' => 'repeated.delete']);
Route::get('/repeatedexpenses/show/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@show', 'as' => 'repeated.show']);
// report controller:
Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']);
@ -314,13 +330,15 @@ Route::group(
// piggy bank controller
Route::post('/piggy_banks/store', ['uses' => 'PiggyBankController@store', 'as' => 'piggy_banks.store']);
Route::post('/piggy_banks/update/{piggy_bank}', ['uses' => 'PiggyBankController@update', 'as' => 'piggy_banks.update']);
Route::post('/piggy_banks/destroy/{piggy_bank}', ['uses' => 'PiggyBankController@destroy', 'as' => 'piggy_banks.destroy']);
Route::post('/piggy_banks/add/{piggy_bank}', ['uses' => 'PiggyBankController@postAdd', 'as' => 'piggy_banks.add']); # add money
Route::post('/piggy_banks/remove/{piggy_bank}', ['uses' => 'PiggyBankController@postRemove', 'as' => 'piggy_banks.remove']); # remove money.
Route::post('/piggy_banks/update/{piggyBank}', ['uses' => 'PiggyBankController@update', 'as' => 'piggy_banks.update']);
Route::post('/piggy_banks/destroy/{piggyBank}', ['uses' => 'PiggyBankController@destroy', 'as' => 'piggy_banks.destroy']);
Route::post('/piggy_banks/add/{piggyBank}', ['uses' => 'PiggyBankController@postAdd', 'as' => 'piggy_banks.add']); # add money
Route::post('/piggy_banks/remove/{piggyBank}', ['uses' => 'PiggyBankController@postRemove', 'as' => 'piggy_banks.remove']); # remove money.
// repeated expense controller
Route::post('/repeatedexpense/store', ['uses' => 'RepeatedExpenseController@store', 'as' => 'repeated.store']);
Route::post('/repeatedexpense/update/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@update', 'as' => 'repeated.update']);
Route::post('/repeatedexpense/destroy/{repeatedExpense}', ['uses' => 'RepeatedExpenseController@destroy', 'as' => 'repeated.destroy']);
// preferences controller
Route::post('/preferences', ['uses' => 'PreferencesController@postIndex']);

View File

@ -1,12 +1,34 @@
@extends('layouts.default')
@section('content')
{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) }}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p>
<a href="{{route('piggy_banks.create')}}" class="btn btn-success">Create new piggy bank</a>
</p>
</div>
</div>
@foreach($piggyBanks as $piggyBank)
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-fw fa-rocket"></i> <a href="{{route('piggy_banks.show',$piggyBank->id)}}" title="{{{$piggyBank->name}}}">{{{$piggyBank->name}}}</a>
<!-- ACTIONS MENU -->
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
Actions
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
<li><a href="{{route('piggy_banks.edit',$piggyBank->id)}}"><i class="fa fa-pencil fa-fw"></i> Edit</a></li>
<li><a href="{{route('piggy_banks.delete',$piggyBank->id)}}"><i class="fa fa-trash fa-fw"></i> Delete</a></li>
</ul>
</div>
</div>
</div>
<div class="panel-body">
<div class="row">
@ -61,19 +83,10 @@
</div>
@endforeach
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-fw fa-plus"></i> Create piggy bank
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-4 col-lg-offset-2 col-md-offset-3 col-sm-offset-4">
<a href="{{route('piggy_banks.create')}}" class="btn btn-success">Create new piggy bank</a>
</div>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12">
<p>
<a href="{{route('piggy_banks.create')}}" class="btn btn-success">Create new piggy bank</a>
</p>
</div>
</div>

View File

@ -0,0 +1,37 @@
@extends('layouts.default')
@section('content')
{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $repeatedExpense) }}
{{Form::open(['class' => 'form-horizontal','id' => 'destroy','url' => route('repeated.destroy',$repeatedExpense->id)])}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<div class="panel panel-red">
<div class="panel-heading">
Delete repeated expense "{{{$repeatedExpense->name}}}"
</div>
<div class="panel-body">
<p>
Are you sure?
</p>
<p>
<button type="submit" class="btn btn-default btn-danger">Delete permanently</button>
<a href="{{URL::previous()}}" class="btn-default btn">Cancel</a >
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<div class="col-sm-8">
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -0,0 +1,97 @@
@extends('layouts.default')
@section('content')
{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $repeatedExpense) }}
{{Form::model($repeatedExpense, ['class' => 'form-horizontal','id' => 'update','url' => route('repeated.update',$repeatedExpense->id)])}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-fw fa-exclamation"></i> Mandatory fields
</div>
<div class="panel-body">
{{Form::ffText('name')}}
{{Form::ffSelect('account_id',$accounts,null,['label' => 'Save on account'])}}
{{Form::ffAmount('targetamount')}}
{{Form::ffDate('targetdate',null,['label' => 'First target date'])}}
{{Form::ffSelect('rep_length',$periods,null,['label' => 'Repeats every'])}}
{{Form::ffInteger('rep_every',null,['label' => 'Skip period'])}}
{{Form::ffInteger('rep_times',null,['label' => 'Repeat times'])}}
</div>
</div>
<p>
<button type="submit" class="btn btn-lg btn-success">
<i class="fa fa-pencil"></i> Update repeated expense
</button>
</p>
</div>
<div class="col-lg-6 col-md-12 col-sm-12">
<!-- panel for optional fields -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-smile-o"></i> Optional fields
</div>
<div class="panel-body">
{{Form::ffCheckbox('remind_me','1',$preFilled['remind_me'],['label' => 'Remind me'])}}
{{Form::ffSelect('reminder',$periods,$preFilled['reminder'],['label' => 'Remind every'])}}
</div>
</div>
<!-- panel for options -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-bolt"></i> Options
</div>
<div class="panel-body">
{{Form::ffOptionsList('update','piggy bank')}}
</div>
</div>
</div>
</div>
{{--
<h4>Mandatory fields</h4>
<h4>Optional fields</h4>
<div class="form-group">
{{ Form::label('reminder', 'Remind you every', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
<input type="number" step="1" min="1" value="{{Input::old('reminder_skip') ?: 1}}" style="width:50px;display:inline;" max="100" name="reminder_skip" class="form-control" />
<select class="form-control" name="reminder" style="width:150px;display: inline">
<option value="none" label="do not remind me">do not remind me</option>
@foreach($periods as $period)
<option value="{{$period}}" label="{{$period}}">{{$period}}</option>
@endforeach
</select>
@if($errors->has('reminder'))
<p class="text-danger">{{$errors->first('reminder')}}</p>
@else
<span class="help-block">Enter a number and a period and Firefly will remind you to add money
to this piggy bank every now and then.</span>
@endif
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<button type="submit" class="btn btn-default btn-success">Create the piggy bank</button>
</div>
</div>
</div>
</div>
--}}
{{Form::close()}}
@stop

View File

@ -4,24 +4,34 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p>
<a class="btn btn-lg btn-success" href="{{route('repeated.create')}}">Create new repeated expense</a>
<a class="btn btn-success" href="{{route('repeated.create')}}">Create new repeated expense</a>
</p>
</div>
</div>
<!-- TODO create update and destroy -->
@foreach($expenses as $entry)
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
</div>
</div>
<!-- TODO create update and destroy -->
<div class="row">
@foreach($expenses as $entry)
<div class="col-lg-3 col-md-4 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<a href="{{route('repeated.show',$entry->id)}}" title="{{{$entry->name}}}">{{{$entry->name}}}</a>
({{mf($entry->targetamount)}})
<!-- ACTIONS MENU -->
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
Actions
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
<li><a href="{{route('repeated.edit',$entry->id)}}"><i class="fa fa-pencil fa-fw"></i> Edit</a></li>
<li><a href="{{route('repeated.delete',$entry->id)}}"><i class="fa fa-trash fa-fw"></i> Delete</a></li>
</ul>
</div>
</div>
</div>
<div class="panel-body">
<div class="progress progress-striped">
@ -40,7 +50,16 @@
</div>
</div>
</div>
</div>
@endforeach
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p>
<a class="btn btn-success" href="{{route('repeated.create')}}">Create new repeated expense</a>
</p>
</div>
</div>

View File

@ -1,6 +1,6 @@
@extends('layouts.default')
@section('content')
{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $piggyBank) }}
{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $repeatedExpense) }}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
@foreach($repetitions as $rep)
@ -21,7 +21,7 @@
</div>
<div class="panel-body">
<p>
Target amount: {{mf($piggyBank->targetamount)}}. Currently saved: {{mf($rep->currentamount)}}.
Target amount: {{mf($repeatedExpense->targetamount)}}. Currently saved: {{mf($rep->currentamount)}}.
</p>
<div class="row">
@foreach($rep->bars as $bar)

View File

@ -42,12 +42,22 @@
</tr>
@foreach($budgets as $id => $budget)
<tr>
<td>{{{$budget['name']}}}</td>
<td>{{{$budget['name']}}}
@if($id == 0)
<i class="fa fa-fw fa-question-circle" data-toggle="tooltip" data-placement="top" title="The calculation used here is slightly different from the row below. The numbers should match."></i>
@endif
</td>
<td>{{mf($budget['amount'])}}</td>
<?php $spent = 0;?>
@foreach($accounts as $account)
@if(isset($account->budgetInformation[$id]))
<td>{{mf($account->budgetInformation[$id]['amount'])}}</td>
<td>
@if($id == 0)
<a href="#">{{mf($account->budgetInformation[$id]['amount'])}}</a>
@else
{{mf($account->budgetInformation[$id]['amount'])}}
@endif
</td>
<?php
$spent += floatval($account->budgetInformation[$id]['amount']);
$accountSums[$account->id] += floatval($account->budgetInformation[$id]['amount']);
@ -61,10 +71,14 @@
</tr>
@endforeach
<tr>
<td colspan="2">Without budget</td>
<td colspan="2">Without budget
<i class="fa fa-fw fa-question-circle" data-toggle="tooltip" data-placement="top" title="The calculation used here is slightly different from the row above. The numbers should match."></i>
</td>
@foreach($accounts as $account)
@if(isset($account->budgetInformation[0]))
<td>{{mf($account->budgetInformation[0]['amount'])}}</td>
<td>
<a href="#">{{mf($account->budgetInformation[0]['amount'])}}</a>
</td>
@else
<td>{{mf(0)}}</td>
@endif
@ -74,7 +88,9 @@
<tr>
<td colspan="2">Balanced by transfers</td>
@foreach($accounts as $account)
<td>{{mf($account->balancedAmount)}}</td>
<td>
<a href="#">{{mf($account->balancedAmount)}}</a>
</td>
@endforeach
<td colspan="2">&nbsp;</td>
</tr>
@ -101,7 +117,9 @@
$accountSums[$account->id] += $account->balancedAmount;
?>
@if(isset($account->budgetInformation[0]))
<td>{{mf($account->budgetInformation[0]['amount'] + $account->balancedAmount)}}</td>
<td>
<a href="#">{{mf($account->budgetInformation[0]['amount'] + $account->balancedAmount)}}</a>
</td>
@else
<td>{{mf(0)}}</td>
@endif
@ -126,4 +144,13 @@
</table>
</div>
</div>
<!-- modal to show various budget information -->
<div class="modal fade" id="budgetModal">
</div>
@stop
@section('scripts')
{{HTML::script('assets/javascript/firefly/reports.js')}}
@stop

View File

@ -1,5 +1,8 @@
$(function () {
$('#help').click(showHelp);
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
});
function showHelp(e) {

View File

@ -1,5 +1,8 @@
<?php
/**
* Class PreferencesControllerCest
*/
class PreferencesControllerCest
{
/**

View File

@ -0,0 +1,23 @@
<?php
/**
* Class RepeatedExpenseCest
*/
class RepeatedExpenseCest {
/**
* @param FunctionalTester $I
*/
public function _after(FunctionalTester $I)
{
}
/**
* @param FunctionalTester $I
*/
public function _before(FunctionalTester $I)
{
$I->amLoggedAs(['email' => 'thegrumpydictator@gmail.com', 'password' => 'james']);
}
}