Fixed validation and made some new form elements.

This commit is contained in:
Sander Dorigo
2014-10-05 08:27:18 +02:00
parent 4ce978b9f3
commit b3209d3b4d
9 changed files with 528 additions and 280 deletions

View File

@@ -4,6 +4,7 @@
use Firefly\Exception\FireflyException;
use Firefly\Helper\Controllers\TransactionInterface as TI;
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
use Illuminate\Support\MessageBag;
/**
* Class TransactionController
@@ -66,9 +67,19 @@ class TransactionController extends BaseController
$piggies = $toolkit->makeSelectList($piggyRepository->get());
$piggies[0] = '(no piggy bank)';
/*
* Catch messages from validation round:
*/
if (Session::has('messages')) {
$messages = Session::get('messages');
Session::forget('messages');
} else {
$messages = new MessageBag;
}
return View::make('transactions.create')->with('accounts', $assetAccounts)->with('budgets', $budgets)->with(
'what', $what
)->with('piggies', $piggies)->with('subTitle', 'Add a new ' . $what);
)->with('piggies', $piggies)->with('subTitle', 'Add a new ' . $what)->with('messages', $messages);
}
/**
@@ -292,7 +303,14 @@ class TransactionController extends BaseController
}
break;
case 'validate_only':
$messageBags = $this->_helper->validate($data);
Session::flash('warnings', $messageBags['warnings']);
Session::flash('successes', $messageBags['successes']);
Session::flash('errors', $messageBags['errors']);
return Redirect::route('transactions.create', [$what])->withInput();
break;
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;

View File

@@ -0,0 +1,202 @@
<?php
namespace Firefly\Form;
use Firefly\Exception\FireflyException;
use Illuminate\Support\MessageBag;
class Form
{
/**
* @param $name
* @param null $value
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffAmount($name, $value = null, array $options = [])
{
$options['step'] = 'any';
$options['min'] = '0.01';
return self::ffInput('number', $name, $value, $options);
}
public static function ffDate($name, $value = null, array $options = [])
{
return self::ffInput('date', $name, $value, $options);
}
/**
* @param $name
* @param array $list
* @param null $selected
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffSelect($name, array $list = [], $selected = null, array $options = [])
{
return self::ffInput('select', $name, $selected, $options, $list);
}
/**
* @param $name
* @param null $value
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffText($name, $value = null, array $options = array())
{
return self::ffInput('text', $name, $value, $options);
}
/**
* @param $type
* @param $name
* @param null $value
* @param array $options
* @param array $list
* @return string
* @throws FireflyException
*/
public static function ffInput($type, $name, $value = null, array $options = array(), $list = [])
{
/*
* add some defaults to this method:
*/
$options['class'] = 'form-control';
$options['id'] = 'ffInput_' . $name;
$options['autocomplete'] = 'off';
$options['type'] = 'text';
/*
* Make label and placeholder look nice.
*/
$options['placeholder'] = isset($options['label']) ? $options['label'] : ucfirst($name);
$options['label'] = isset($options['label']) ? $options['label'] : ucfirst($name);
if ($name == 'account_id') {
$options['label'] = 'Asset account';
}
$options['label'] = str_replace(['_'], [' '], $options['label']);
$options['placeholder'] = str_replace(['_'], [' '], $options['placeholder']);
/*
* Get the value.
*/
if (!is_null(\Input::old($name))) {
/*
* Old value overrules $value.
*/
$value = \Input::old($name);
}
/*
* Get errors, warnings and successes from session:
*/
/** @var MessageBag $errors */
$errors = \Session::get('errors');
/** @var MessageBag $warnings */
$warnings = \Session::get('warnings');
/** @var MessageBag $successes */
$successes = \Session::get('successes');
/*
* If errors, add some more classes.
*/
switch (true) {
case (!is_null($errors) && $errors->has($name)):
$classes = 'form-group has-error has-feedback';
break;
case (!is_null($warnings) && $warnings->has($name)):
$classes = 'form-group has-warning has-feedback';
break;
case (!is_null($successes) && $successes->has($name)):
$classes = 'form-group has-success has-feedback';
break;
default:
$classes = 'form-group';
break;
}
/*
* Add some HTML.
*/
$label = isset($options['label']) ? $options['label'] : ucfirst($name);
$html = '<div class="' . $classes . '">';
$html .= '<label for="' . $options['id'] . '" class="col-sm-4 control-label">' . $label . '</label>';
$html .= '<div class="col-sm-8">';
/*
* Switch input type:
*/
switch ($type) {
case 'text':
$html .= \Form::input('text', $name, $value, $options);
break;
case 'number':
$html .= '<div class="input-group"><div class="input-group-addon">&euro;</div>';
$html .= \Form::input('number', $name, $value, $options);
$html .= '</div>';
break;
case 'date':
$html .= \Form::input('date', $name, $value, $options);
break;
case 'select':
$html .= \Form::select($name, $list, $value, $options);
break;
default:
throw new FireflyException('Cannot handle type "' . $type . '" in FFFormBuilder.');
break;
}
/*
* If errors, respond to them:
*/
if (!is_null($errors)) {
if ($errors->has($name)) {
$html .= '<span class="glyphicon glyphicon-remove form-control-feedback"></span>';
$html .= '<p class="text-danger">' . e($errors->first($name)) . '</p>';
}
}
unset($errors);
/*
* If warnings, respond to them:
*/
if (!is_null($warnings)) {
if ($warnings->has($name)) {
$html .= '<span class="glyphicon glyphicon-warning-sign form-control-feedback"></span>';
$html .= '<p class="text-warning">' . e($warnings->first($name)) . '</p>';
}
}
unset($warnings);
/*
* If successes, respond to them:
*/
if (!is_null($successes)) {
if ($successes->has($name)) {
$html .= '<span class="glyphicon glyphicon-ok form-control-feedback"></span>';
$html .= '<p class="text-success">' . e($successes->first($name)) . '</p>';
}
}
unset($successes);
$html .= '</div>';
$html .= '</div>';
return $html;
}
}

View File

@@ -2,6 +2,9 @@
namespace Firefly\Helper\Controllers;
use Carbon\Carbon;
use Exception;
use Firefly\Exception\FireflyException;
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
use Firefly\Storage\Category\CategoryRepositoryInterface as CRI;
@@ -179,6 +182,186 @@ class Transaction implements TransactionInterface
}
/**
* Returns messages about the validation.
*
* @param array $data
* @return array
* @throws FireflyException
*/
public function validate(array $data)
{
$errors = new MessageBag;
$warnings = new MessageBag;
$successes = new MessageBag;
/*
* Description:
*/
if (strlen($data['description']) == 0) {
$errors->add('description', 'The description should not be this short.');
}
if (strlen($data['description']) > 250) {
$errors->add('description', 'The description should not be this long.');
}
/*
* Amount
*/
if (floatval($data['amount']) <= 0) {
$errors->add('amount', 'The amount cannot be zero or less than zero.');
}
if (floatval($data['amount']) > 10000) {
$warnings->add('amount', 'OK, but that\'s a lot of money dude.');
}
/*
* Date
*/
try {
$date = new Carbon($data['date']);
} catch (Exception $e) {
$errors->add('date', 'The date entered was invalid');
}
if (strlen($data['date']) == 0) {
$errors->add('date', 'The date entered was invalid');
}
if (!$errors->has('date')) {
$successes->add('date', 'OK!');
}
/*
* Category
*/
$category = $this->_categories->findByName($data['category']);
if (strlen($data['category']) == 0) {
$warnings->add('category', 'No category will be created.');
} else {
if (is_null($category)) {
$warnings->add('category', 'Will have to be created.');
} else {
$successes->add('category', 'OK!');
}
}
switch ($data['what']) {
default:
throw new FireflyException('Cannot validate a ' . $data['what']);
break;
case 'deposit':
/*
* Tests for deposit
*/
// asset account
$accountId = isset($data['account_id']) ? intval($data['account_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_id', 'Cannot find this asset account.');
} else {
$successes->add('account_id', 'OK!');
}
// revenue account:
if (strlen($data['revenue_account']) == 0) {
$warnings->add('revenue_account', 'Revenue account will be "cash".');
} else {
$exp = $this->_accounts->findRevenueAccountByName($data['revenue_account'], false);
if (is_null($exp)) {
$warnings->add('revenue_account', 'Expense account will be created.');
} else {
$successes->add('revenue_account', 'OK!');
}
}
break;
case 'transfer':
// account from
$accountId = isset($data['account_from_id']) ? intval($data['account_from_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_from_id', 'Cannot find this asset account.');
} else {
$successes->add('account_from_id', 'OK!');
}
unset($accountId);
// account to
$accountId = isset($data['account_to_id']) ? intval($data['account_to_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_to_id', 'Cannot find this asset account.');
} else {
$successes->add('account_to_id', 'OK!');
}
unset($accountId);
// piggy bank
$piggybankId = isset($data['piggybank_id']) ? intval($data['piggybank_id']) : 0;
$piggybank = $this->_piggybanks->find($piggybankId);
if (is_null($piggybank)) {
$warnings->add('piggybank_id', 'No piggy bank will be modified.');
} else {
$successes->add('piggybank_id', 'OK!');
}
break;
case 'withdrawal':
/*
* Tests for withdrawal
*/
// asset account
$accountId = isset($data['account_id']) ? intval($data['account_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_id', 'Cannot find this asset account.');
} else {
$successes->add('account_id', 'OK!');
}
// expense account
if (strlen($data['expense_account']) == 0) {
$warnings->add('expense_account', 'Expense account will be "cash".');
} else {
$exp = $this->_accounts->findExpenseAccountByName($data['expense_account'], false);
if (is_null($exp)) {
$warnings->add('expense_account', 'Expense account will be created.');
} else {
$successes->add('expense_account', 'OK!');
}
}
// budget
if (!isset($data['budget_id']) || (isset($data['budget_id']) && intval($data['budget_id']) == 0)) {
$warnings->add('budget_id', 'No budget selected.');
} else {
$budget = $this->_budgets->find(intval($data['budget_id']));
if (is_null($budget)) {
$errors->add('budget_id', 'This budget does not exist');
} else {
$successes->add('budget_id', 'OK!');
}
}
break;
}
if (count($errors->get('description')) == 0) {
$successes->add('description', 'OK!');
}
if (count($errors->get('amount')) == 0) {
$successes->add('amount', 'OK!');
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
/*
* Tests for deposit
*/
/*
* Tests for transfer
*/
}
/**
* Store a full transaction journal and associated stuff
*
@@ -247,8 +430,7 @@ class Transaction implements TransactionInterface
/*
* Add a custom error when they are the same.
*/
if ($to->id ==
$from->id) {
if ($to->id == $from->id) {
$bag = new MessageBag;
$bag->add('account_from_id', 'The account from cannot be the same as the account to.');
return $bag;

View File

@@ -19,6 +19,15 @@ interface TransactionInterface {
*/
public function store(array $data);
/**
* Returns messages about the validation.
*
* @param array $data
*
* @return array
*/
public function validate(array $data);
/**
* @param \TransactionJournal $journal
* @param array $data

View File

@@ -83,17 +83,19 @@ interface AccountRepositoryInterface
/**
* @param $name
* @param $create
*
* @return |Account|null
*/
public function findExpenseAccountByName($name);
public function findExpenseAccountByName($name, $create = true);
/**
* @param $name
* @param $create
*
* @return |Account|null
*/
public function findRevenueAccountByName($name);
public function findRevenueAccountByName($name, $create = true);
/**
* @return mixed

View File

@@ -90,10 +90,11 @@ class EloquentAccountRepository implements AccountRepositoryInterface
* because when you feed it "Import account" it will always return an import account of that type.
*
* @param $name
* @param $create
*
* @return null|\Account
*/
public function findExpenseAccountByName($name)
public function findExpenseAccountByName($name, $create = true)
{
$cashType = $this->findAccountType('Cash account');
$importType = $this->findAccountType('Import account');
@@ -118,7 +119,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
)->first(['accounts.*']);
// create if not found:
if (strlen($name) > 0 && is_null($account)) {
if (strlen($name) > 0 && is_null($account) && $create === true) {
$type = $this->findAccountType('Expense account');
$set = [
'name' => $name,
@@ -127,6 +128,8 @@ class EloquentAccountRepository implements AccountRepositoryInterface
'account_type_id' => $type->id
];
$account = $this->firstOrCreate($set);
} else if (strlen($name) > 0 && is_null($account) && $create === false) {
return null;
}
@@ -171,10 +174,11 @@ class EloquentAccountRepository implements AccountRepositoryInterface
/**
* @param $name
* @param $create
*
* @return |Account|null
*/
public function findRevenueAccountByName($name)
public function findRevenueAccountByName($name, $create = true)
{
// catch Import account:
if ($name == 'Import account') {
@@ -195,7 +199,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
$account = $this->_user->accounts()->where('name', $name)->where('account_type_id', $type->id)->first();
// create if not found:
if (strlen($name) > 0) {
if (strlen($name) > 0 && is_null($account) && $create === true) {
$set = [
'name' => $name,
'user_id' => $this->_user->id,
@@ -203,6 +207,8 @@ class EloquentAccountRepository implements AccountRepositoryInterface
'account_type_id' => $type->id
];
$account = $this->firstOrCreate($set);
} else if (strlen($name) > 0 && is_null($account) && $create === false) {
return null;
}
// find cash account as fall back:

View File

@@ -1,25 +1,14 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p class="lead">
Accounts are your personal accounts that represent value.
</p>
<p class="text-info">
"Asset accounts" are your personal accounts that represent value. For example: bank accounts, saving
accounts, stock, etc.
</p>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p>
<a href="{{route('accounts.create','asset')}}" class="btn btn-success">Create a new asset account</a>
<a href="{{route('accounts.create','asset')}}" class="btn btn-success btn-large">Create a new asset account</a>
</p>
@if(count($accounts) > 0)
@include('accounts.list')
<p>
<a href="{{route('accounts.create','asset')}}" class="btn btn-success">Create a new asset account</a>
<a href="{{route('accounts.create','asset')}}" class="btn btn-success btn-large">Create a new asset account</a>
</p>
@endif
</div>

View File

@@ -1,20 +1,5 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p class="lead">
Something about accounts.
</p>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<p class="text-info">
Something about accounts here!
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','route' => 'accounts.store'])}}
{{Form::hidden('what',$what)}}
<div class="row">

View File

@@ -12,144 +12,37 @@
</div>
<div class="panel-body">
<!-- DESCRIPTION ALWAYS AVAILABLE -->
<div
@if($errors->has('description'))
class="form-group has-error has-feedback"
@else
class="form-group"
@endif
>
<label for="description" class="col-sm-4 control-label">Description</label>
<div class="col-sm-8">
<input
type="text" name="description"
value="{{{Input::old('description') ?: Input::get('description')}}}"
placeholder="Description"
autocomplete="off"
class="form-control" />
@if($errors->has('description'))
<p class="text-danger">{{$errors->first('description')}}</p>
@endif
</div>
</div>
<!-- SHOW ACCOUNT (FROM) ONLY FOR WITHDRAWALS AND DEPOSITS -->
{{Form::ffText('description')}}
@if($what == 'deposit' || $what == 'withdrawal')
<div class="form-group">
<label for="account_id" class="col-sm-4 control-label">
Asset account
</label>
<div class="col-sm-8">
{{Form::select('account_id',$accounts,Input::old('account_id') ?: Input::get('account_id'),['class' => 'form-control'])}}
@if($errors->has('account_id'))
<p class="text-danger">{{$errors->first('account_id')}}</p>
@endif
</div>
</div>
{{Form::ffSelect('account_id',$accounts)}}
@endif
<!-- SHOW EXPENSE ACCOUNT ONLY FOR WITHDRAWALS -->
@if($what == 'withdrawal')
<div class="form-group">
<label for="expense_account" class="col-sm-4 control-label">Expense account</label>
<div class="col-sm-8">
<input
type="text" name="expense_account" value="{{{Input::old('expense_account')}}}"
autocomplete="off" class="form-control" placeholder="Expense account" />
@if($errors->has('expense_account'))
<p class="text-danger">{{$errors->first('expense_account')}}</p>
@else
<span class="help-block">
This field will auto-complete your existing expense accounts (where you spent the
money), but you can type freely to create new ones. If you took the money from
an ATM, you should leave this field empty.</span>
@endif
</div>
</div>
{{Form::ffText('expense_account')}}
@endif
<!-- SHOW REVENUE ACCOUNT ONLY FOR DEPOSITS -->
@if($what == 'deposit')
<div class="form-group">
<label for="revenue_account" class="col-sm-4 control-label">
Revenue account
</label>
<div class="col-sm-8">
<input type="text"
name="revenue_account" value="{{{Input::old('revenue_account')}}}"
autocomplete="off" class="form-control" placeholder="Revenue account" />
@if($errors->has('beneficiary'))
<p class="text-danger">{{$errors->first('revenue_account')}}</p>
@else
<span class="help-block">
This field will auto-complete your existing revenue
accounts (where you spent the receive money from),
but you can type freely to create new ones.</span>
@endif
</div>
</div>
{{Form::ffText('revenue_account')}}
@endif
<!-- ONLY SHOW FROM/TO ACCOUNT WHEN CREATING TRANSFER -->
@if($what == 'transfer')
<div class="form-group">
<label for="account_from_id" class="col-sm-4 control-label">Account from</label>
<div class="col-sm-8">
{{Form::select('account_from_id',$accounts,Input::old('account_from_id') ?: Input::get('account_from_id'),['class' => 'form-control'])}}
@if($errors->has('account_from_id'))
<p class="text-danger">{{$errors->first('account_from_id')}}</p>
{{Form::ffSelect('account_from_id',$accounts)}}
{{Form::ffSelect('account_to_id',$accounts)}}
@endif
</div>
</div>
<div class="form-group">
<label for="account_to_id" class="col-sm-4 control-label">Account to</label>
<div class="col-sm-8">
{{Form::select('account_to_id',$accounts,Input::old('account_to_id') ?: Input::get('account_to_id'),['class' => 'form-control'])}}
@if($errors->has('account_to_id'))
<p class="text-danger">{{$errors->first('account_to_id')}}</p>
@endif
</div>
</div>
@endif
<!-- ALWAYS SHOW AMOUNT -->
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">
@if($what == 'withdrawal')
Amount spent
@endif
@if($what == 'deposit')
Amount received
@endif
@if($what == 'transfer')
Amount transferred
@endif
</label>
<div class="col-sm-8">
<input type="number"
name="amount" min="0.01"
value="{{Input::old('amount') ?: Input::get('amount')}}"
step="any" class="form-control" />
@if($errors->has('amount'))
<p class="text-danger">{{$errors->first('amount')}}</p>
@endif
</div>
</div>
{{Form::ffAmount('amount')}}
<!-- ALWAYS SHOW DATE -->
<div class="form-group">
<label for="date" class="col-sm-4 control-label">Date</label>
<div class="col-sm-8">
<input type="date"
name="date" value="{{Input::old('date') ?: date('Y-m-d')}}" class="form-control" />
@if($errors->has('date'))
<p class="text-danger">{{$errors->first('date')}}</p>
@endif
{{Form::ffDate('date', date('Y-m-d'))}}
</div>
</div>
</div>
</div>
<p>
<button type="submit" class="btn btn-lg btn-success">
<i class="fa fa-plus-circle"></i> Store new {{{$what}}}
@@ -166,55 +59,17 @@
<div class="panel-body">
<!-- BUDGET ONLY WHEN CREATING A WITHDRAWAL -->
@if($what == 'withdrawal')
<div class="form-group">
<label for="budget_id" class="col-sm-4 control-label">Budget</label>
<div class="col-sm-8">
{{Form::select('budget_id',$budgets,Input::old('budget_id') ?: 0,['class' => 'form-control'])}}
@if($errors->has('budget_id'))
<p class="text-danger">{{$errors->first('budget_id')}}</p>
@else
<span class="help-block">Select one of your budgets to make this transaction a part of it.</span>
@endif
</div>
</div>
{{Form::ffSelect('budget_id',$budgets,0)}}
@endif
<!-- CATEGORY ALWAYS -->
<div class="form-group">
<label for="category" class="col-sm-4 control-label">Category</label>
<div class="col-sm-8">
<input type="text" name="category" value="{{Input::old('category')}}" autocomplete="off" class="form-control" placeholder="Category" />
@if($errors->has('category'))
<p class="text-danger">{{$errors->first('category')}}</p>
@else
<span class="help-block">Add more fine-grained information to
this transaction by entering a category. This field will auto-complete
existing categories but can also be used to create new ones.
</span>
@endif
</div>
</div>
{{Form::ffText('category')}}
<!-- TAGS -->
<!-- RELATE THIS TRANSFER TO A PIGGY BANK -->
@if($what == 'transfer' && count($piggies) > 0)
<div class="form-group">
<label for="piggybank_id" class="col-sm-4 control-label">
Piggy bank
</label>
<div class="col-sm-8">
{{Form::select('piggybank_id',$piggies,Input::old('piggybank_id') ?: 0,['class' => 'form-control'])}}
@if($errors->has('piggybank_id'))
<p class="text-danger">{{$errors->first('piggybank_id')}}</p>
@else
<span class="help-block">
You can directly add the amount you're transferring
to one of your piggy banks, provided they are related to the account your
transferring <em>to</em>.
</span>
@endif
</div>
</div>
{{Form::ffSelect('piggybank_id',$piggies,0)}}
@endif
</div>
</div>