Some more work done. Mainly accounts. [skip ci]

This commit is contained in:
James Cole 2014-07-26 08:05:02 +02:00
parent 30d5b88769
commit d088b2c558
25 changed files with 932 additions and 282 deletions

View File

@ -8,12 +8,14 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
class AccountController extends \BaseController
{
protected $_accounts;
/**
* @param ARI $accounts
*/
public function __construct(ARI $accounts)
{
$this->accounts = $accounts;
$this->_accounts = $accounts;
View::share('menu', 'accounts');
}
@ -25,7 +27,7 @@ class AccountController extends \BaseController
*/
public function index()
{
$all = $this->accounts->get();
$all = $this->_accounts->get();
$list = [
@ -68,23 +70,38 @@ class AccountController extends \BaseController
{
return View::make('accounts.create');
}
//
//
// /**
// * Store a newly created resource in storage.
// *
// * @return Response
// */
// public function store()
// {
// $account = $this->accounts->store();
// if($account === false) {
// Session::flash('error','Could not create account with provided information');
// return Redirect::route('accounts.create')->withInput()->withErrors($this->accounts->validator);
// }
// }
//
//
public function store()
{
$account = $this->_accounts->store(Input::all());
if (!$account->id) {
// did not save, return with error:
Session::flash('error', 'Could not save the new account. Please check the form.');
return View::make('accounts.create')->withErrors($account->errors());
} else {
// saved! return to wherever.
Session::flash('success', 'Account "' . $account->name . '" created!');
if (Input::get('create') == '1') {
return Redirect::route('accounts.create')->withInput();
} else {
return Redirect::route('accounts.index');
}
}
}
public function edit($accountId)
{
$account = $this->_accounts->find($accountId);
if ($account) {
// find the initial balance transaction, if any:
$openingBalance = $this->_accounts->findOpeningBalanceTransaction($account);
return View::make('accounts.edit')->with('account', $account)->with('openingBalance',$openingBalance);
}
}
/**
* Display the specified resource.
*
@ -94,44 +111,44 @@ class AccountController extends \BaseController
*/
public function show($accountId)
{
return $accountId;
$account = $this->_accounts->find($accountId);
return View::make('accounts.show')->with('account',$account);
}
//
//
// /**
// * Show the form for editing the specified resource.
// *
// * @param int $id
// * @return Response
// */
// public function edit($id)
// {
// //
// }
//
//
// /**
// * Update the specified resource in storage.
// *
// * @param int $id
// * @return Response
// */
// public function update($id)
// {
// //
// }
//
//
// /**
// * Remove the specified resource from storage.
// *
// * @param int $id
// * @return Response
// */
// public function destroy($id)
// {
// //
// }
/**
* Update the specified resource in storage.
*
* @return Response
*/
public function update()
{
$account = $this->_accounts->update(Input::all());
Session::flash('success','Account "'.$account->name.'" updated.');
return Redirect::route('accounts.index');
}
public function delete($accountId) {
$account = $this->_accounts->find($accountId);
if($account) {
return View::make('accounts.delete')->with('account',$account);
}
}
/**
* @param $accountId
*/
public function destroy()
{
$result = $this->_accounts->destroy(Input::get('id'));
if($result === true) {
Session::flash('success','The account was deleted.');
return Redirect::route('accounts.index');
} else {
Session::flash('danger','Could not delete the account. Check the logs to be sure.');
return Redirect::route('accounts.index');
}
}
}

View File

@ -208,8 +208,11 @@ class ChartController extends BaseController
foreach ($budget->limits as $limit) {
foreach ($limit->limitrepetitions as $rep) {
//0: envelope for period:
$data['series'][0]['data'][] = floatval($rep->amount);
$data['series'][1]['data'][] = $rep->spent;
$amount = floatval($rep->amount);
$spent = $rep->spent;
$color = $spent > $amount ? '#FF0000' : null;
$data['series'][0]['data'][] = $amount;
$data['series'][1]['data'][] = ['y' => $rep->spent, 'color' => $color];
}
}

View File

@ -112,7 +112,7 @@ class TransactionController extends BaseController
$journal->categories()->save($category);
}
Session::flash('success', 'Transaction saved');
Session::flash('success', 'Transaction "' . $description . '" saved');
if (Input::get('create') == '1') {
return Redirect::route('transactions.create', $what)->withInput();
@ -153,12 +153,127 @@ class TransactionController extends BaseController
*/
public function edit($journalId)
{
// get journal:
$journal = $this->_journal->find($journalId);
if ($journal) {
// type is useful for display:
$what = strtolower($journal->transactiontype->type);
// some lists prefilled:
$budgets = $this->_budgets->getAsSelectList();
$budgets[0] = '(no budget)';
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
return View::make('transactions.edit')->with('journal', $journal)->with('accounts', $accounts);
// data to properly display form:
$data = [
'date' => $journal->date->format('Y-m-d'),
'category' => '',
'budget_id' => 0
];
$category = $journal->categories()->first();
if (!is_null($category)) {
$data['category'] = $category->name;
}
switch ($journal->transactiontype->type) {
case 'Withdrawal':
$data['account_id'] = $journal->transactions[0]->account->id;
$data['beneficiary'] = $journal->transactions[1]->account->name;
$data['amount'] = floatval($journal->transactions[1]->amount);
$budget = $journal->budgets()->first();
if (!is_null($budget)) {
$data['budget_id'] = $budget->id;
}
break;
case 'Deposit':
$data['account_id'] = $journal->transactions[1]->account->id;
$data['beneficiary'] = $journal->transactions[0]->account->name;
$data['amount'] = floatval($journal->transactions[1]->amount);
break;
case 'Transfer':
$data['account_from_id'] = $journal->transactions[1]->account->id;
$data['account_to_id'] = $journal->transactions[0]->account->id;
$data['amount'] = floatval($journal->transactions[1]->amount);
break;
}
return View::make('transactions.edit')->with('journal', $journal)->with('accounts', $accounts)->with(
'what', $what
)->with('budgets', $budgets)->with('data', $data);
}
return View::make('error')->with('message', 'Invalid journal');
}
public function update($journalId)
{
// get journal:
$journal = $this->_journal->find($journalId);
if ($journal) {
// update basics first:
$journal->description = Input::get('description');
$journal->date = Input::get('date');
$amount = floatval(Input::get('amount'));
// remove previous category, if any:
if (!is_null($journal->categories()->first())) {
$journal->categories()->detach($journal->categories()->first()->id);
}
// remove previous budget, if any:
if (!is_null($journal->budgets()->first())) {
$journal->budgets()->detach($journal->budgets()->first()->id);
}
// get the category:
$category = $this->_categories->findByName(Input::get('category'));
if (!is_null($category)) {
$journal->categories()->attach($category);
}
// update the amounts:
$journal->transactions[0]->amount = $amount * -1;
$journal->transactions[1]->amount = $amount;
// switch on type to properly change things:
switch ($journal->transactiontype->type) {
case 'Withdrawal':
// means transaction[0] is the users account.
$account = $this->_accounts->find(Input::get('account_id'));
$beneficiary = $this->_accounts->findByName(Input::get('beneficiary'));
$journal->transactions[0]->account()->associate($account);
$journal->transactions[1]->account()->associate($beneficiary);
// do budget:
$budget = $this->_budgets->find(Input::get('budget_id'));
$journal->budgets()->attach($budget);
break;
case 'Deposit':
// means transaction[0] is the beneficiary.
$account = $this->_accounts->find(Input::get('account_id'));
$beneficiary = $this->_accounts->findByName(Input::get('beneficiary'));
$journal->transactions[0]->account()->associate($beneficiary);
$journal->transactions[1]->account()->associate($account);
break;
case 'Transfer':
// means transaction[0] is account that sent the money (from).
$fromAccount = $this->_accounts->find(Input::get('account_from_id'));
$toAccount = $this->_accounts->find(Input::get('account_to_id'));
$journal->transactions[0]->account()->associate($fromAccount);
$journal->transactions[1]->account()->associate($toAccount);
break;
default:
throw new \Firefly\Exception\FireflyException('Cannot edit this!');
break;
}
$journal->transactions[0]->save();
$journal->transactions[1]->save();
$journal->save();
return Redirect::route('transactions.edit', $journal->id);
}
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Firefly\Helper\Form;
/**
* Class FormHelper
*
* @package Firefly\Form
*/
class FormHelper
{
public function budget($value = null)
{
$str = '<select name="budget_id" class="form-control">';
$str .= '<option value="0" label="(no budget)"';
if (is_null($value) || intval($value) == 0) {
$str .= ' selected="selected"';
}
$str .= '</option>';
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgets */
$budgets = \App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
$list = $budgets->getAsSelectList();
foreach ($list as $id => $name) {
$str .= '<option value="' . e($id) . '" label="' . e($name) . '"';
if ($id == intval($value)) {
$str .= ' selected="selected"';
}
$str .= '>' . e($name) . '</option>';
}
$str .= '</select>';
return $str;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 25/07/14
* Time: 21:04
*/
namespace Firefly\Helper\Form;
use Illuminate\Events\Dispatcher;
/**
* Class FormTrigger
*
* @package Firefly\Helper\Form
*/
class FormTrigger {
public function registerFormExtensions() {
\Form::macro(
'budget', function () {
$helper = new \Firefly\Helper\Form\FormHelper;
return $helper->budget();
}
);
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen('laravel.booted', 'Firefly\Helper\Form\FormTrigger@registerFormExtensions');
}
}

View File

@ -59,6 +59,13 @@ interface AccountRepositoryInterface
*/
public function getDefault();
/**
* @param \Account $account
*
* @return mixed
*/
public function findOpeningBalanceTransaction(\Account $account);
/**
* @return mixed
*/
@ -72,18 +79,25 @@ interface AccountRepositoryInterface
/**
* @param $data
*
* @return mixed
* @return \Account
*/
public function store($data);
/**
* @param $data
* @param Carbon $date
* @param int $amount
* @param $accountId
*
* @return mixed
* @return bool
*/
public function storeWithInitialBalance($data, Carbon $date, $amount = 0);
public function destroy($accountId);
/**
* @param $data
*
* @return \Account
*/
public function update($data);
/**
* @param $name

View File

@ -4,8 +4,6 @@
namespace Firefly\Storage\Account;
use Carbon\Carbon;
use Firefly\Exception\FireflyException;
use Illuminate\Database\QueryException;
/**
* Class EloquentAccountRepository
@ -14,8 +12,6 @@ use Illuminate\Database\QueryException;
*/
class EloquentAccountRepository implements AccountRepositoryInterface
{
public $validator;
/**
*
*/
@ -45,16 +41,6 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return $list;
}
/**
* @param $accountId
*
* @return mixed
*/
public function find($accountId)
{
return \Auth::user()->accounts()->where('id', $accountId)->first();
}
/**
* @param $ids
*
@ -118,73 +104,6 @@ class EloquentAccountRepository implements AccountRepositoryInterface
}
/**
* @param $data
* @param Carbon $date
* @param int $amount
*
* @return \Account|mixed
* @throws \Firefly\Exception\FireflyException
*/
public function storeWithInitialBalance($data, Carbon $date, $amount = 0)
{
$account = $this->store($data);
$initialBalanceAT = \AccountType::where('description', 'Initial balance account')->first();
$initial = new \Account;
$initial->accountType()->associate($initialBalanceAT);
$initial->user()->associate(\Auth::user());
$initial->name = $data['name'] . ' initial balance';
$initial->active = 0;
try {
$initial->save();
} catch (QueryException $e) {
\Log::error('DB ERROR: ' . $e->getMessage());
throw new FireflyException('Could not save counterbalance account for ' . $data['name']);
}
// create new transaction journal (and transactions):
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $transactionJournal */
$transactionJournal = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$transactionJournal->createSimpleJournal(
$initial, $account, 'Initial Balance for ' . $data['name'], $amount, $date
);
return $account;
}
/**
* @param $data
*
* @return \Account|mixed
* @throws \Firefly\Exception\FireflyException
*/
public function store($data)
{
$defaultAT = \AccountType::where('description', 'Default account')->first();
$at = isset($data['account_type']) ? $data['account_type'] : $defaultAT;
$account = new \Account;
$account->accountType()->associate($at);
$account->user()->associate(\Auth::user());
$account->name = $data['name'];
$account->active = isset($data['active']) ? $data['active'] : 1;
try {
$account->save();
} catch (QueryException $e) {
\Log::error('DB ERROR: ' . $e->getMessage());
throw new FireflyException('Could not save account ' . $data['name']);
}
return $account;
}
/**
* @param $name
*
@ -210,7 +129,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
$beneficiary = $this->findByName($name);
if (!$beneficiary) {
$data = [
'name' => $name,
'name' => $name,
'account_type' => $type
];
return $this->store($data);
@ -228,6 +147,142 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return \Auth::user()->accounts()->where('name', 'like', '%' . $name . '%')->first();
}
/**
* @param $data
*
* @return \Account
* @throws \Firefly\Exception\FireflyException
*/
public function store($data)
{
$defaultAccountType = \AccountType::where('description', 'Default account')->first();
$accountType = isset($data['account_type']) ? $data['account_type'] : $defaultAccountType;
// create Account:
$account = new \Account;
$account->accountType()->associate($accountType);
$account->user()->associate(\Auth::user());
$account->name = $data['name'];
$account->active
= isset($data['active']) && intval($data['active']) >= 0 && intval($data['active']) <= 1 ? intval(
$data['active']
) : 1;
// try to save it:
if ($account->save()) {
// create initial balance, if necessary:
if (isset($data['openingbalance']) && isset($data['openingbalancedate'])) {
$amount = floatval($data['openingbalance']);
$date = new Carbon($data['openingbalancedate']);
$this->_createInitialBalance($account, $amount, $date);
}
}
// whatever the result, return the account.
return $account;
}
/**
* @param \Account $account
* @param int $amount
* @param Carbon $date
*
* @return bool
*/
protected function _createInitialBalance(\Account $account, $amount = 0, Carbon $date)
{
// get account type:
$initialBalanceAT = \AccountType::where('description', 'Initial balance account')->first();
// create new account:
$initial = new \Account;
$initial->accountType()->associate($initialBalanceAT);
$initial->user()->associate(\Auth::user());
$initial->name = $account->name . ' initial balance';
$initial->active = 0;
if ($initial->validate()) {
$initial->save();
// create new transaction journal (and transactions):
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $transactionJournal */
$transactionJournal = \App::make(
'Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface'
);
$transactionJournal->createSimpleJournal(
$initial, $account, 'Initial Balance for ' . $account->name, $amount, $date
);
return true;
}
return false;
}
/**
* @param $data
*
* @return \Account|void
*/
public function update($data)
{
$account = $this->find($data['id']);
if ($account) {
// update account accordingly:
$account->name = $data['name'];
if ($account->validate()) {
$account->save();
}
// update initial balance if necessary:
if ($account->accounttype->description == 'Default account') {
$journal = $this->findOpeningBalanceTransaction($account);
$journal->date = new Carbon($data['openingbalancedate']);
$journal->transactions[0]->amount = floatval($data['openingbalance']) * -1;
$journal->transactions[1]->amount = floatval($data['openingbalance']);
$journal->transactions[0]->save();
$journal->transactions[1]->save();
$journal->save();
}
}
return $account;
}
public function destroy($accountId) {
$account = $this->find($accountId);
if($account) {
$account->delete();
return true;
}
return false;
}
/**
* @param $accountId
*
* @return mixed
*/
public function find($accountId)
{
return \Auth::user()->accounts()->where('id', $accountId)->first();
}
/**
* @param \Account $account
*
* @return mixed|void
*/
public function findOpeningBalanceTransaction(\Account $account)
{
$transactionType = \TransactionType::where('type', 'Opening balance')->first();
$journal = \TransactionJournal::
with(
['transactions' => function ($q) {
$q->orderBy('amount', 'ASC');
}]
)->where('transaction_type_id', $transactionType->id)
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)->first(['transaction_journals.*']);
return $journal;
}
/**
* @return mixed
*/

View File

@ -40,6 +40,9 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*/
public function findByName($name)
{
if($name == '') {
return null;
}
return \Auth::user()->categories()->where('name', 'LIKE', '%' . $name . '%')->first();
}

View File

@ -22,7 +22,9 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
public function find($journalId)
{
return \Auth::user()->transactionjournals()->with(
['transactions', 'transactioncurrency', 'transactiontype', 'components', 'transactions.account',
['transactions' => function ($q) {
return $q->orderBy('amount', 'ASC');
}, 'transactioncurrency', 'transactiontype', 'components', 'transactions.account',
'transactions.account.accounttype']
)
->where('id', $journalId)->first();

View File

@ -27,6 +27,8 @@ Route::group(['before' => 'auth'], function () {
Route::get('/accounts', ['uses' => 'AccountController@index', 'as' => 'accounts.index']);
Route::get('/accounts/create', ['uses' => 'AccountController@create', 'as' => 'accounts.create']);
Route::get('/accounts/{account}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']);
Route::get('/accounts/{account}/edit', ['uses' => 'AccountController@edit', 'as' => 'accounts.edit']);
Route::get('/accounts/{account}/delete', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']);
// budget controller:
Route::get('/budget/create',['uses' => 'BudgetController@create', 'as' => 'budgets.create']);
@ -71,7 +73,9 @@ Route::group(['before' => 'csrf|auth'], function () {
Route::post('/preferences', ['uses' => 'PreferencesController@postIndex']);
// account controller:
Route::get('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']);
Route::post('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']);
Route::post('/accounts/update', ['uses' => 'AccountController@update', 'as' => 'accounts.update']);
Route::post('/accounts/destroy', ['uses' => 'AccountController@destroy', 'as' => 'accounts.destroy']);
// limit controller:
Route::post('/budgets/limits/store', ['uses' => 'LimitController@store', 'as' => 'budgets.limits.store']);

View File

@ -1,66 +1,109 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Add a new account</small>
<small>Add a new personal account</small>
</h1>
<p class="lead">
Accounts are the record holders for transactions and transfers. Money moves
from one account to another.
</p>
<p>
<p class="text-info">
In a double-entry bookkeeping system (such as this one) there is a "from" account and a "to"
account, even when money is created from thin air (such as interest, or when new accounts already have
a positive balance).
</p>
<p>
<p class="text-info"><span class="text-danger">This form creates personal accounts only.</span>
If this is your first account, it should be a checking or savings account. Enter its name and if relevant
the current balance. Check your bank statements for the last current balance you can find.
</p>
{{Form::open(['class' => 'form-horizontal','url' => route('accounts.store')])}}
<div class="form-group">
{{ Form::label('name', 'Account name', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
{{ Form::text('name', Input::old('name'), ['class' => 'form-control']) }}
@if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<p class="text-info">Use something descriptive such as "checking account" or "My Bank Main Account".</p>
@endif
</div>
</div>
<div class="form-group">
{{ Form::label('openingbalance', 'Opening balance', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
{{ Form::input('number','openingbalance', Input::old('openingbalance'), ['step' => 'any', 'class' => 'form-control'])}}
@if($errors->has('openingbalance'))
<p class="text-danger">{{$errors->first('openingbalance')}}</p>
@else
<p class="text-info">What's the current balance of this new account?</p>
@endif
</div>
</div>
<div class="form-group">
{{ Form::label('openingbalancedate', 'Opening balance date', ['class' => 'col-sm-3 control-label'])}}
<div class="col-sm-9">
{{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: date('Y-m-d'), ['class' => 'form-control']) }}
@if($errors->has('openingbalancedate'))
<p class="text-danger">{{$errors->first('openingbalancedate')}}</p>
@else
<p class="text-info">When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account.</p>
@endif
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-default">Create my first account</button>
</div>
</div>
</form>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','route' => 'accounts.store'])}}
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<h4>Mandatory fields</h4>
<div class="form-group">
{{ Form::label('name', 'Account name', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
{{ Form::text('name', Input::old('name'), ['class' => 'form-control']) }}
@if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<span
class="help-block">Use something descriptive such as "checking account" or "My Bank Main Account".</span>
@endif
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<h4>Optional fields</h4>
<div class="form-group">
{{ Form::label('openingbalance', 'Opening balance', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
<div class="input-group">
<span class="input-group-addon">&euro;</span>
{{Form::input('number','openingbalance', Input::old('openingbalance'), ['step' => 'any', 'class' =>
'form-control'])}}
</div>
@if($errors->has('openingbalance'))
<p class="text-danger">{{$errors->first('openingbalance')}}</p>
@else
<span class="help-block">What's the current balance of this new account?</span>
@endif
</div>
</div>
<div class="form-group">
{{ Form::label('openingbalancedate', 'Opening balance date', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
{{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: date('Y-m-d'), ['class'
=> 'form-control']) }}
@if($errors->has('openingbalancedate'))
<p class="text-danger">{{$errors->first('openingbalancedate')}}</p>
@else
<span class="help-block">When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account.</span>
@endif
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<!-- add another after this one? -->
<div class="form-group">
<label for="create" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<div class="checkbox">
<label>
{{Form::checkbox('create',1,Input::old('create') == '1')}}
Create another (return to this form)
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<button type="submit" class="btn btn-default btn-success">Create the account</button>
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -0,0 +1,46 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Delete "{{{$account->name}}}"</small>
</h1>
<p class="lead">
Remember that deleting something is permanent.
</p>
</div>
</div>
{{Form::model($account, ['class' => 'form-horizontal','url' => route('accounts.destroy')])}}
{{Form::hidden('id',$account->id)}}
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
@if($account->transactions()->count() > 0)
<p class="text-info">
Account "{{{$account->name}}}" still has {{$account->transactions()->count()}} transaction(s) associated to it.
These will be deleted as well.
</p>
@endif
<p class="text-danger">
Press "Delete permanently" If you are sure you want to delete "{{{$account->name}}}".
</p>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<div class="col-sm-8">
<button type="submit" class="btn btn-default btn-danger">Delete permanently</button>
<a href="{{route('accounts.index')}}" class="btn-default btn">Cancel</a>
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -0,0 +1,94 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Edit "{{{$account->name}}}"</small>
</h1>
<p class="lead">
Accounts are the record holders for transactions and transfers. Money moves
from one account to another.
</p>
</div>
</div>
{{Form::model($account, ['class' => 'form-horizontal','url' => route('accounts.update')])}}
{{Form::hidden('id',$account->id)}}
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<h4>Mandatory fields</h4>
<div class="form-group">
{{ Form::label('name', 'Account name', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
{{ Form::text('name', Input::old('name'), ['class' => 'form-control']) }}
@if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<span
class="help-block">Use something descriptive such as "checking account" or "Albert Heijn".</span>
@endif
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
@if($account->accounttype->description == 'Default account')
<h4>Optional fields</h4>
<div class="form-group">
{{ Form::label('openingbalance', 'Opening balance', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
<div class="input-group">
<span class="input-group-addon">&euro;</span>
@if(!is_null($openingBalance))
{{Form::input('number','openingbalance', Input::old('openingbalance') ?: $openingBalance->transactions[1]->amount, ['step' => 'any', 'class' => 'form-control'])}}
@else
{{Form::input('number','openingbalance', Input::old('openingbalance'), ['step' => 'any', 'class' => 'form-control'])}}
@endif
</div>
@if($errors->has('openingbalance'))
<p class="text-danger">{{$errors->first('openingbalance')}}</p>
@else
<span class="help-block">What's the current balance of this new account?</span>
@endif
</div>
</div>
<div class="form-group">
{{ Form::label('openingbalancedate', 'Opening balance date', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
@if(!is_null($openingBalance))
{{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: $openingBalance->date->format('Y-m-d'), ['class' => 'form-control']) }}
@else
{{ Form::input('date','openingbalancedate', Input::old('openingbalancedate') ?: date('Y-m-d'), ['class' => 'form-control']) }}
@endif
@if($errors->has('openingbalancedate'))
<p class="text-danger">{{$errors->first('openingbalancedate')}}</p>
@else
<span class="help-block">When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account.</span>
@endif
</div>
</div>
@endif
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<button type="submit" class="btn btn-default btn-success">Update {{{$account->name}}}</button>
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -5,8 +5,10 @@
<h1>Firefly
<small>Accounts</small>
</h1>
<h2>Index</h2>
<p style="width:50%;" class="text-info">
<p class="lead">
Accounts are the record holders for transactions and transfers. Money moves from one account to another.
</p>
<p class="text-info">
In a double-entry bookkeeping system almost <em>everything</em> is an account. Your own personal
bank accounts are representated as accounts (naturally), but the stores you buy stuff at are also
represented as accounts. Likewise, if you have a job, your salary is drawn from their account.

View File

@ -3,6 +3,7 @@
<th>&nbsp;</th>
<th style="width:30%;">Name</th>
<th>Current balance</th>
<th></th>
</tr>
@foreach($accounts as $account)
<tr>
@ -14,6 +15,12 @@
<td>
<a href="{{route('accounts.show',$account->id)}}" title="Overview for account {{{$account->name}}}">{{{$account->name}}}</a></td>
<td>{{mf($account->balance())}}</td>
<td>
<span class="btn-group-xs btn-group">
<a href="{{route('accounts.edit',$account->id)}}" title="Edit {{{$account->name}}}" class="btn btn-default"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="{{route('accounts.delete',$account->id)}}" title="Edit {{{$account->name}}}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
</span>
</td>
</tr>
@endforeach
</table>

View File

@ -0,0 +1,29 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Overview for account "{{{$account->name}}}"</small>
</h1>
</div>
</div>
@include('partials.date_nav')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div id="chart"></div>
</div>
</div>
@stop
@section('scripts')
<script type="text/javascript">
var accountID = {{$account->id}};
</script>
<script src="assets/javascript/highcharts.js"></script>
<script src="assets/javascript/highcharts-more.js"></script>
<script src="assets/javascript/highslide-full.min.js"></script>
<script src="assets/javascript/highslide.config.js"></script>
<script src="assets/javascript/accounts.js"></script>
@stop

View File

@ -1,29 +0,0 @@
@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>
<p>
@if($group == 'budget')
<a class="btn btn-default" href ="{{route('budgets.index','date')}}"><span class="glyphicon glyphicon-th-list"></span> Group by date</a>
@else
<a class="btn btn-default" href ="{{route('budgets.index','budget')}}"><span class="glyphicon glyphicon-th-list"></span> Group by budget</a>
@endif
</p>
</div>
</div>
@if($group == 'budget')
@include('budgets.index-budget')
@else
@include('budgets.index-date')
@endif
@stop

View File

@ -1,6 +1,17 @@
@extends('layouts.default')
@section('content')
@include('partials.date_nav')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
@if($count > 0)
<small>What's playing?</small>
@endif
</h1>
</div>
</div>
@if($count > 0)
@include('partials.date_nav')
@endif
@if($count == 0)
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
@ -66,9 +77,10 @@
<div class="col-lg-12 col-md-12 col-sm-12">
<h4>Budgets</h4>
<div id="budgets">
</div>
<div id="budgets"></div>
<p>
<a class="btn btn-default btn-xs" href ="{{route('budgets.limits.create')}}?startdate={{Session::get('start')->format('Y-m-d')}}"><span class="glyphicon glyphicon-plus-sign"></span> Create a limit</a>
</p>
</div>
</div>

View File

@ -1,12 +1,6 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
@if($count > 0)
<small>What's playing?</small>
@endif
</h1>
@if($count > 0)
<form role="form" method="GET">
<form role="form" method="GET" action="{{Request::url()}}">
<?php $r = Session::get('range', '1M'); ?>
<div class="row">
<div class="col-lg-2">
@ -55,7 +49,6 @@
</form>
@endif
</div>
</div>

View File

@ -171,6 +171,7 @@
</div>
</div>
</div>
{{Form::close()}}
@stop
@section('scripts')

View File

@ -3,7 +3,7 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Edit transaction ""</small>
<small>Edit transaction "{{{$journal->description}}}"</small>
</h1>
</div>
</div>
@ -14,61 +14,101 @@
account <em>A</em> to account <em>B</em>.
</p>
<p class="text-info">
A deposit is when you earn money, moving an amount from a beneficiary into your own account.
Updating a transaction will also update balances, budgets and other records.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal'])}}
{{Form::open(['class' => 'form-horizontal','url' => route('transactions.update',$journal->id)])}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Mandatory fields</h4>
<!-- ALWAYS AVAILABLE -->
<div class="form-group">
<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')}}}" autocomplete="off" class="form-control" placeholder="Description" />
<input type="text" name="description" value="{{{Input::old('description') ?: $journal->description}}}" autocomplete="off" class="form-control" placeholder="Description" />
</div>
</div>
<!-- SHOW ACCOUNT (FROM) ONLY FOR WITHDRAWALS AND DEPOSITS -->
@if($what == 'deposit' || $what == 'withdrawal')
<div class="form-group">
<label for="beneficiary" class="col-sm-4 control-label">Beneficiary (payer)</label>
<label for="account_id" class="col-sm-4 control-label">
@if($what == 'deposit')
Receiving account
@endif
@if($what == 'withdrawal')
Paid from account
@endif
</label>
<div class="col-sm-8">
<input type="text" name="beneficiary" value="{{{Input::old('beneficiary')}}}" autocomplete="off" class="form-control" placeholder="Beneficiary" />
{{Form::select('account_id',$accounts,Input::old('account_id') ?: $data['account_id'],['class' => 'form-control'])}}
</div>
</div>
@endif
<!-- SHOW BENEFICIARY (ACCOUNT TO) ONLY FOR WITHDRAWALS AND DEPOSITS -->
@if($what == 'deposit' || $what == 'withdrawal')
<div class="form-group">
<label for="beneficiary" class="col-sm-4 control-label">
@if($what == 'deposit')
Paying beneficiary
@endif
@if($what == 'withdrawal')
Beneficiary
@endif
</label>
<div class="col-sm-8">
<input type="text" name="beneficiary" value="{{{Input::old('beneficiary') ?: $data['beneficiary']}}}" autocomplete="off" class="form-control" placeholder="Beneficiary" />
<span class="help-block">This field will auto-complete your existing beneficiaries (if any), but you can type freely to create new ones.</span>
</div>
</div>
@endif
<!-- ONLY SHOW FROM/TO ACCOUNT WHEN CREATING TRANSFER -->
@if($what == 'transfer')
<div class="form-group">
<label for="account_id" class="col-sm-4 control-label">Account</label>
<label for="account_from_id" class="col-sm-4 control-label">Account from</label>
<div class="col-sm-8">
{{Form::select('account_id',$accounts,Input::old('account_id'),['class' => 'form-control'])}}
{{Form::select('account_to_id',$accounts,Input::old('account_from_id') ?: $data['account_from_id'],['class' => 'form-control'])}}
</div>
</div>
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">Amount spent</label>
<label for="account_to_id" class="col-sm-4 control-label">Account to</label>
<div class="col-sm-8">
<input type="number" name="amount" min="0.01" value="{{Input::old('amount') or 0}}" step="any" class="form-control" />
{{Form::select('account_from_id',$accounts,Input::old('account_to_id') ?: $data['account_to_id'],['class' => 'form-control'])}}
</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') ?: $data['amount']}}" step="any" class="form-control" />
</div>
</div>
<!-- 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" />
</div>
</div>
<div class="form-group">
<label for="submit" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<input type="submit" name="submit" value="Create deposit" class="btn btn-info" />
<input type="date" name="date" value="{{Input::old('date') ?: $data['date']}}" class="form-control" />
</div>
</div>
@ -76,10 +116,21 @@
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Optional fields</h4>
<!-- 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') ?: $data['budget_id'],['class' => 'form-control'])}}
<span class="help-block">Select one of your budgets to make this transaction a part of it.</span>
</div>
</div>
@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="" autocomplete="off" class="form-control" placeholder="Category" />
<input type="text" name="category" value="{{Input::old('category') ?: $data['category']}}" autocomplete="off" class="form-control" placeholder="Category" />
<span class="help-block">Add more fine-grained information to this transaction by entering a category.
Like the beneficiary-field, this field will auto-complete existing categories but can also be used
to create new ones.
@ -88,7 +139,20 @@
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<!-- ALWAYS SHOW SUBMit -->
<div class="form-group">
<label for="submit" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<input type="submit" name="submit" value="Update {{$what}}" class="btn btn-info" />
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop

View File

@ -49,7 +49,15 @@
<a href="#">{{{$journal->transactions[1]->account->name}}}</a>
@endif
</td>
<td>Edit / delete</td>
<td>
<div class="btn-group btn-group-xs">
<a href="{{route('transactions.edit',$journal->id)}}" class="btn btn-default">
<span class="glyphicon glyphicon-pencil"></span>
<a href="{{route('transactions.delete',$journal->id)}}" class="btn btn-danger">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
</td>
</tr>
@endforeach
</table>

View File

@ -12,6 +12,7 @@
*/
if (!function_exists('mf')) {
function mf($n, $coloured = true)
{
@ -35,6 +36,8 @@ if (!function_exists('mf')) {
$app = new Illuminate\Foundation\Application;
/*
|--------------------------------------------------------------------------
| Detect The Application Environment
@ -83,6 +86,8 @@ $framework = $app['path.base'] .
require $framework . '/Illuminate/Foundation/start.php';
Event::subscribe('Firefly\Helper\Form\FormTrigger');
/*
|--------------------------------------------------------------------------
| Return The Application
@ -97,5 +102,11 @@ require $framework . '/Illuminate/Foundation/start.php';
// do something with events:
Event::subscribe('Firefly\Trigger\Limits\EloquentLimitTrigger');
App::booted(function() {
$forms = new \Firefly\Helper\Form\FormTrigger;
$forms->registerFormExtensions();
}
);
return $app;

View File

@ -0,0 +1,95 @@
$(function () {
if($('#chart').length == 1) {
/**
* get data from controller for home charts:
*/
$.getJSON('chart/home/account/' + accountID).success(function (data) {
var options = {
chart: {
renderTo: 'chart',
type: 'line'
},
series: data,
title: {
text: 'BETTER TITLE HERE'
},
yAxis: {
formatter: function () {
return '$' + Highcharts.numberFormat(this.y, 0);
}
},
subtitle: {
text: '<a href="#">View more</a>',
useHTML: true
},
xAxis: {
floor: 0,
type: 'datetime',
dateTimeLabelFormats: {
day: '%e %b',
year: '%b'
},
title: {
text: 'Date'
}
},
tooltip: {
shared: true,
crosshairs: false,
formatter: function () {
var str = '<span style="font-size:80%;">' + Highcharts.dateFormat("%A, %e %B", this.x) + '</span><br />';
for (x in this.points) {
var point = this.points[x];
var colour = point.point.pointAttr[''].fill;
str += '<span style="color:' + colour + '">' + point.series.name + '</span>: € ' + Highcharts.numberFormat(point.y, 2) + '<br />';
}
//console.log();
return str;
return '<span style="font-size:80%;">' + this.series.name + ' on ' + Highcharts.dateFormat("%e %B", this.x) + ':</span><br /> € ' + Highcharts.numberFormat(this.y, 2);
}
},
plotOptions: {
line: {
shadow: true
},
series: {
cursor: 'pointer',
negativeColor: '#FF0000',
threshold: 0,
lineWidth: 1,
marker: {
radius: 2
},
point: {
events: {
click: function (e) {
hs.htmlExpand(null, {
src: 'chart/home/info/' + this.series.name + '/' + Highcharts.dateFormat("%d/%m/%Y", this.x),
pageOrigin: {
x: e.pageX,
y: e.pageY
},
objectType: 'ajax',
headingText: '<a href="#">' + this.series.name + '</a>',
width: 250
}
)
;
}
}
}
}
},
credits: {
enabled: false
}
};
$('#chart').highcharts(options);
});
}
});

View File

@ -197,6 +197,7 @@ $(function () {
bar: {
dataLabels: {
enabled: true,
formatter: function() {return '€ ' + Highcharts.numberFormat(this.y,2);}
}
}
},
@ -215,24 +216,6 @@ $(function () {
enabled: false
},
series: data.series
// [
// {
// name: 'Budget in X',
// data: [107, 31, 635, 203, 2]
// },
// {
// name: 'Expense in X',
// data: [107, 31, 635, 203, 2]
// },
// {
// name: 'Budget now',
// data: [133, 156, 947, 408, 6]
// },
// {
// name: 'Expense now',
// data: [973, 914, 454, 732, 34]
// }
// ]
});
});