- Updated transaction controller to need less code for the same work.

- Small feedback bug in migration controller
- Better database create scripts.
- Fixed bug in seed scripts.
- Cleanup and fixed sorting in various helpers
- Extended some tests to catch changed code.
- Created show(journal) and edit(journal) (untested)

[skip-ci]
This commit is contained in:
James Cole
2014-07-16 21:11:43 +02:00
parent 552224b73f
commit 12ae548dab
20 changed files with 616 additions and 237 deletions

View File

@@ -35,6 +35,9 @@ class MigrationController extends BaseController
$migration->loadFile($file);
if ($migration->validFile()) {
$migration->migrate();
} else {
echo 'Invalid file.';
exit();
}
}
}

View File

@@ -34,26 +34,7 @@ class TransactionController extends BaseController
View::share('menu', 'home');
}
/**
* @return $this|\Illuminate\View\View
*/
public function createWithdrawal()
{
// get accounts with names and id's.
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
$budgets = $this->_budgets->getAsSelectList();
$budgets[0] = '(no budget)';
return View::make('transactions.withdrawal')->with('accounts', $accounts)->with('budgets', $budgets);
}
/**
* @return $this|\Illuminate\View\View
*/
public function createDeposit()
public function create($what)
{
// get accounts with names and id's.
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
@@ -62,38 +43,39 @@ class TransactionController extends BaseController
$budgets[0] = '(no budget)';
return View::make('transactions.deposit')->with('accounts', $accounts)->with('budgets', $budgets);
return View::make('transactions.create')->with('accounts', $accounts)->with('budgets', $budgets)->with(
'what', $what
);
}
/**
* @return $this|\Illuminate\View\View
*/
public function createTransfer()
public function store($what)
{
// get accounts with names and id's.
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
// $fromAccount and $toAccount are found
// depending on the $what
$budgets = $this->_budgets->getAsSelectList();
$fromAccount = null;
$toAccount = null;
$budgets[0] = '(no budget)';
return View::make('transactions.transfer')->with('accounts', $accounts)->with('budgets', $budgets);
}
/**
* @return \Illuminate\Http\RedirectResponse
*/
public function postCreateWithdrawal()
{
// create or find beneficiary:
$beneficiary = $this->_accounts->createOrFindBeneficiary(Input::get('beneficiary'));
// fall back to cash account if empty:
if (is_null($beneficiary)) {
$beneficiary = $this->_accounts->getCashAccount();
switch ($what) {
case 'withdrawal':
$fromAccount = $this->_accounts->find(intval(Input::get('account_id')));
$toAccount = $this->_accounts->createOrFindBeneficiary(Input::get('beneficiary'));
break;
case 'deposit':
$fromAccount = $this->_accounts->createOrFindBeneficiary(Input::get('beneficiary'));
$toAccount = $this->_accounts->find(intval(Input::get('account_id')));
break;
case 'transfer':
$fromAccount = $this->_accounts->find(intval(Input::get('account_from_id')));
$toAccount = $this->_accounts->find(intval(Input::get('account_to_id')));
break;
}
// fall back to cash if necessary:
if (is_null($fromAccount)) {
$fromAccount = $this->_accounts->getCashAccount();
}
if (is_null($toAccount)) {
$toAccount = $this->_accounts->getCashAccount();
}
// create or find category:
@@ -102,9 +84,6 @@ class TransactionController extends BaseController
// find budget:
$budget = $this->_budgets->find(intval(Input::get('budget_id')));
// find account:
$account = $this->_accounts->find(intval(Input::get('account_id')));
// find amount & description:
$description = trim(Input::get('description'));
$amount = floatval(Input::get('amount'));
@@ -113,9 +92,9 @@ class TransactionController extends BaseController
// create journal
/** @var \TransactionJournal $journal */
try {
$journal = $this->_journal->createSimpleJournal($account, $beneficiary, $description, $amount, $date);
$journal = $this->_journal->createSimpleJournal($fromAccount, $toAccount, $description, $amount, $date);
} catch (\Firefly\Exception\FireflyException $e) {
return Redirect::route('transactions.withdrawal')->withInput();
return Redirect::route('transactions.create', $what)->withInput();
}
// attach bud/cat (?)
@@ -128,80 +107,27 @@ class TransactionController extends BaseController
Session::flash('success', 'Transaction saved');
return Redirect::route('index');
}
/**
* @return \Illuminate\Http\RedirectResponse
*/
public function postCreateDeposit()
public function show($journalId)
{
// create or find beneficiary:
$beneficiary = $this->_accounts->createOrFindBeneficiary(Input::get('beneficiary'));
// fall back to cash account if empty:
if (is_null($beneficiary)) {
$beneficiary = $this->_accounts->getCashAccount();
$journal = $this->_journal->find($journalId);
if ($journal) {
return View::make('transactions.show')->with('journal', $journal);
}
// create or find category:
$category = $this->_categories->createOrFind(Input::get('category'));
// find account:
$account = $this->_accounts->find(intval(Input::get('account_id')));
// find amount & description:
$description = trim(Input::get('description'));
$amount = floatval(Input::get('amount'));
$date = new \Carbon\Carbon(Input::get('date'));
// create journal
/** @var \TransactionJournal $journal */
try {
$journal = $this->_journal->createSimpleJournal($beneficiary, $account, $description, $amount, $date);
} catch (\Firefly\Exception\FireflyException $e) {
return Redirect::route('transactions.deposit')->withInput();
}
if (!is_null($category)) {
$journal->categories()->save($category);
}
Session::flash('success', 'Transaction saved');
return Redirect::route('index');
return View::make('error')->with('message', 'Invalid journal');
}
/**
* @return \Illuminate\Http\RedirectResponse
*/
public function postCreateTransfer()
public function edit($journalId)
{
// create or find category:
$category = $this->_categories->createOrFind(Input::get('category'));
// find account to:
$toAccount = $this->_accounts->find(intval(Input::get('account_to_id')));
// find account from
$from = $this->_accounts->find(intval(Input::get('account_from_id')));
// find amount & description:
$description = trim(Input::get('description'));
$amount = floatval(Input::get('amount'));
$date = new \Carbon\Carbon(Input::get('date'));
// create journal
/** @var \TransactionJournal $journal */
try {
$journal = $this->_journal->createSimpleJournal($from, $toAccount, $description, $amount, $date);
} catch (\Firefly\Exception\FireflyException $e) {
return Redirect::route('transactions.transfer')->withInput();
$journal = $this->_journal->find($journalId);
if ($journal) {
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
return View::make('transactions.edit')->with('journal', $journal)->with('accounts', $accounts);
}
if (!is_null($category)) {
$journal->categories()->save($category);
}
Session::flash('success', 'Transaction saved');
return Redirect::route('index');
return View::make('error')->with('message', 'Invalid journal');
}
}

View File

@@ -16,6 +16,7 @@ class CreateTransactionJournalsTable extends Migration {
{
$table->increments('id');
$table->timestamps();
$table->integer('user_id')->unsigned();
$table->integer('transaction_type_id')->unsigned();
$table->integer('transaction_currency_id')->unsigned();
$table->string('description',255)->nullable();
@@ -31,6 +32,11 @@ class CreateTransactionJournalsTable extends Migration {
$table->foreign('transaction_currency_id')
->references('id')->on('transaction_currencies')
->onDelete('cascade');
// connect users
$table->foreign('user_id')
->references('id')->on('users')
->onDelete('cascade');
});
}

View File

@@ -1,42 +1,49 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateTransactionsTable extends Migration {
class CreateTransactionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('transactions', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->integer('account_id')->integer();
$table->integer('transaction_journal_id')->integer()->unsigned();
$table->string('description',255)->nullable();
$table->decimal('amount',10,2);
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create(
'transactions', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->integer('account_id')->unsigned();
$table->integer('transaction_journal_id')->unsigned();
$table->string('description', 255)->nullable();
$table->decimal('amount', 10, 2);
// connect transactions to transaction journals
$table->foreign('transaction_journal_id')
->references('id')->on('transaction_journals')
->onDelete('cascade');
// connect transactions to transaction journals
$table->foreign('transaction_journal_id')
->references('id')->on('transaction_journals')
->onDelete('cascade');
});
}
// connect account id:
$table->foreign('account_id')
->references('id')->on('accounts')
->onDelete('cascade');
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('transactions');
}
}
);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('transactions');
}
}

View File

@@ -12,9 +12,10 @@ class DefaultUserSeeder extends Seeder
'password' => Hash::make('sander'),
'reset' => null,
'remember_token' => null,
'migrated' => false
'migrated' => 0
]
);
}
}

View File

@@ -74,7 +74,8 @@ class MigrationHelper implements MigrationHelperInterface
$cashAT = \AccountType::where('description', 'Cash account')->first();
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$cash = $accounts->store(['name' => 'Cash account', 'account_type' => $cashAT, 'active' => false]);
$cash = $accounts->store(['name' => 'Cash account', 'account_type' => $cashAT, 'active' => 0]);
\Log::info('Created cash account (#'.$cash->id.')');
$this->map['cash'] = $cash;
}

View File

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

View File

@@ -9,32 +9,52 @@ use Firefly\Exception\FireflyException;
class EloquentTransactionJournalRepository implements TransactionJournalRepositoryInterface
{
public function find($journalId)
{
return \Auth::user()->transactionjournals()->with(
['transactions', 'transactioncurrency', 'transactiontype', 'components', 'transactions.account',
'transactions.account.accounttype']
)
->where('id', $journalId)->first();
}
/*
*
*/
/**
*
* We're building this thinking the money goes from A to B.
* If the amount is negative however, the money still goes
* from A to B but the balances are reversed.
*
* Aka:
*
* Amount = 200
* A loses 200 (-200). * -1
* B gains 200 (200). * 1
*
* Final balance: -200 for A, 200 for B.
*
* When the amount is negative:
*
* Amount = -200
* A gains 200 (200). * -1
* B loses 200 (-200). * 1
*
* @param \Account $from
* @param \Account $to
* @param $description
* @param $amount
* @param \Carbon\Carbon $date
*
* @return \TransactionJournal
* @throws \Firefly\Exception\FireflyException
*/
public function createSimpleJournal(\Account $from, \Account $to, $description, $amount, \Carbon\Carbon $date)
{
\Log::debug('Creating tranaction "' . $description . '".');
/*
* We're building this thinking the money goes from A to B.
* If the amount is negative however, the money still goes
* from A to B but the balances are reversed.
*
* Aka:
*
* Amount = 200
* A loses 200 (-200). * -1
* B gains 200 (200). * 1
*
* Final balance: -200 for A, 200 for B.
*
* When the amount is negative:
*
* Amount = -200
* A gains 200 (200). * -1
* B loses 200 (-200). * 1
*
*/
// amounts:
$amountFrom = $amount * -1;
$amountTo = $amount;
@@ -61,10 +81,8 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
$journalType = \TransactionType::where('type', 'Opening balance')->first();
break;
// both are yours:
case ($fromAT == 'Default account' && $toAT == 'Default account'):
// determin transaction type. If both accounts are new, it's an initial
// balance transfer.
case ($fromAT == 'Default account' && $toAT == 'Default account'): // both are yours:
// determin transaction type. If both accounts are new, it's an initial balance transfer.
$journalType = \TransactionType::where('type', 'Transfer')->first();
break;
case ($amount < 0):
@@ -102,6 +120,7 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
$journal = new \TransactionJournal();
$journal->transactionType()->associate($journalType);
$journal->transactionCurrency()->associate($currency);
$journal->user()->associate(\Auth::user());
$journal->completed = false;
$journal->description = $description;
$journal->date = $date;
@@ -184,7 +203,7 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
// has to be one:
if (!isset($journal->transactions[0])) {
throw new FireflyException('Journal #' . $journal->id . ' has ' . count($journal->transactions)
. ' transactions!');
. ' transactions!');
}
$transaction = $journal->transactions[0];
$amount = floatval($transaction->amount);
@@ -201,6 +220,10 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
}
unset($journal, $transaction, $budget, $name, $amount);
// sort
arsort($result);
return $result;
}
@@ -233,6 +256,8 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
)
->after($start)->before($end)
->whereIn('transaction_type_id', $types)
->orderBy('date', 'DESC')
->orderBy('id', 'DESC')
->get(['transaction_journals.*']);
foreach ($journals as $journal) {
foreach ($journal->transactions as $t) {
@@ -244,6 +269,10 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
}
}
}
// sort result:
arsort($result);
return $result;
}

View File

@@ -9,6 +9,8 @@ interface TransactionJournalRepositoryInterface
public function get();
public function find($journalId);
public function getByAccount(\Account $account, $count = 25);
public function homeBudgetChart(\Carbon\Carbon $start, \Carbon\Carbon $end);

View File

@@ -6,32 +6,32 @@ use LaravelBook\Ardent\Ardent;
/**
* TransactionJournal
*
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $transaction_type_id
* @property integer $transaction_currency_id
* @property string $description
* @property boolean $completed
* @property \Carbon\Carbon $date
* @property-read \TransactionType $transactionType
* @property-read \TransactionCurrency $transactionCurrency
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $transaction_type_id
* @property integer $transaction_currency_id
* @property string $description
* @property boolean $completed
* @property \Carbon\Carbon $date
* @property-read \TransactionType $transactionType
* @property-read \TransactionCurrency $transactionCurrency
* @property-read \Illuminate\Database\Eloquent\Collection|\Transaction[] $transactions
* @property-read \Illuminate\Database\Eloquent\Collection|\Component[] $components
* @property-read \Illuminate\Database\Eloquent\Collection|\Component[] $components
* @property-read \Illuminate\Database\Eloquent\Collection|\
* 'Budget[] $budgets
* @property-read \Illuminate\Database\Eloquent\Collection|\
* 'Category[] $categories
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereTransactionTypeId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereTransactionCurrencyId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDescription($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereCompleted($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDate($value)
* @method static \TransactionJournal after($date)
* @method static \TransactionJournal before($date)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereTransactionTypeId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereTransactionCurrencyId($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDescription($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereCompleted($value)
* @method static \Illuminate\Database\Query\Builder|\TransactionJournal whereDate($value)
* @method static \TransactionJournal after($date)
* @method static \TransactionJournal before($date)
*/
class TransactionJournal extends Ardent
{
@@ -51,6 +51,7 @@ class TransactionJournal extends Ardent
'transaction_currency_id' => 'factory|TransactionCurrency',
'description' => 'string',
'completed' => '1',
'user_id' => 'factory|User',
'date' => 'date|Y-m-d'
];
@@ -59,6 +60,17 @@ class TransactionJournal extends Ardent
return $this->belongsTo('TransactionType');
}
/**
* User
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('User');
}
public function transactionCurrency()
{
return $this->belongsTo('TransactionCurrency');

View File

@@ -91,4 +91,8 @@ class User extends Ardent implements UserInterface, RemindableInterface
return $this->hasMany('Category');
}
public function transactionjournals() {
return $this->hasMany('TransactionJournal');
}
}

View File

@@ -32,9 +32,13 @@ Route::group(['before' => 'auth'], function () {
// transaction controller:
Route::get('/transactions/add/withdrawal', ['uses' => 'TransactionController@createWithdrawal', 'as' => 'transactions.withdrawal']);
Route::get('/transactions/add/deposit', ['uses' => 'TransactionController@createDeposit', 'as' => 'transactions.deposit']);
Route::get('/transactions/add/transfer', ['uses' => 'TransactionController@createTransfer', 'as' => 'transactions.transfer']);
Route::get('/transactions/create/{what}', ['uses' => 'TransactionController@create', 'as' => 'transactions.create'])
->where(['what' => 'withdrawal|deposit|transfer']);
Route::get('/transaction/show/{id}',['uses' => 'TransactionController@show','as' => 'transactions.show']);
Route::get('/transaction/edit/{id}',['uses' => 'TransactionController@edit','as' => 'transactions.edit']);
Route::get('/transaction/delete/{id}',['uses' => 'TransactionController@delete','as' => 'transactions.delete']);
// migration controller
Route::get('/migrate', ['uses' => 'MigrationController@index', 'as' => 'migrate']);
@@ -56,9 +60,9 @@ Route::group(['before' => 'csrf|auth'], function () {
Route::get('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']);
// transaction controller:
Route::post('/transactions/add/withdrawal', ['uses' => 'TransactionController@postCreateWithdrawal']);
Route::post('/transactions/add/deposit', ['uses' => 'TransactionController@postCreateDeposit']);
Route::post('/transactions/add/transfer', ['uses' => 'TransactionController@postCreateTransfer']);
Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])
->where(['what' => 'withdrawal|deposit|transfer']);
Route::post('/transaction/update/{id}',['uses' => 'TransactionController@update','as' => 'transactions.update']);
}
);

View File

@@ -28,7 +28,10 @@ class TransactionControllerTest extends TestCase
$set = [0 => '(no budget)'];
View::shouldReceive('share');
View::shouldReceive('make')->with('transactions.withdrawal')->andReturn(\Mockery::self())
View::shouldReceive('make')->with('transactions.create')->andReturn(\Mockery::self())
->shouldReceive('with')->once()
->with('what','withdrawal')
->andReturn(Mockery::self())
->shouldReceive('with')->once()
->with('accounts', [])
->andReturn(Mockery::self())
@@ -45,7 +48,7 @@ class TransactionControllerTest extends TestCase
// call
$this->call('GET', '/transactions/add/withdrawal');
$this->call('GET', '/transactions/create/withdrawal');
// test
$this->assertResponseOk();
@@ -56,7 +59,11 @@ class TransactionControllerTest extends TestCase
$set = [0 => '(no budget)'];
View::shouldReceive('share');
View::shouldReceive('make')->with('transactions.deposit')->andReturn(\Mockery::self())
View::shouldReceive('make')->with('transactions.create')->andReturn(\Mockery::self())
->shouldReceive('with')->once()
->with('what','deposit')
->andReturn(Mockery::self())
->shouldReceive('with')->once()
->with('accounts', [])
->andReturn(Mockery::self())
@@ -73,7 +80,7 @@ class TransactionControllerTest extends TestCase
// call
$this->call('GET', '/transactions/add/deposit');
$this->call('GET', '/transactions/create/deposit');
// test
$this->assertResponseOk();
@@ -84,7 +91,10 @@ class TransactionControllerTest extends TestCase
$set = [0 => '(no budget)'];
View::shouldReceive('share');
View::shouldReceive('make')->with('transactions.transfer')->andReturn(\Mockery::self())
View::shouldReceive('make')->with('transactions.create')->andReturn(\Mockery::self())
->shouldReceive('with')->once()
->with('what', 'transfer')
->andReturn(Mockery::self())
->shouldReceive('with')->once()
->with('accounts', [])
->andReturn(Mockery::self())
@@ -101,7 +111,7 @@ class TransactionControllerTest extends TestCase
// call
$this->call('GET', '/transactions/add/transfer');
$this->call('GET', '/transactions/create/transfer');
// test
$this->assertResponseOk();
@@ -148,7 +158,7 @@ class TransactionControllerTest extends TestCase
$tj->shouldReceive('createSimpleJournal')->once()->andReturn($journal);
// call
$this->call('POST', '/transactions/add/withdrawal', $data);
$this->call('POST', '/transactions/store/withdrawal', $data);
// test
$this->assertRedirectedToRoute('index');
@@ -186,8 +196,13 @@ class TransactionControllerTest extends TestCase
$tj = $this->mock('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$tj->shouldReceive('createSimpleJournal')->once()->andReturn($journal);
// mock budget repository
$budgets = $this->mock('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budgets->shouldReceive('createOrFind')->with('')->andReturn(null);
$budgets->shouldReceive('find')->andReturn(null);
// call
$this->call('POST', '/transactions/add/deposit', $data);
$this->call('POST', '/transactions/store/deposit', $data);
// test
$this->assertRedirectedToRoute('index');
@@ -225,8 +240,13 @@ class TransactionControllerTest extends TestCase
$tj = $this->mock('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$tj->shouldReceive('createSimpleJournal')->once()->andReturn($journal);
// mock budget repository
$budgets = $this->mock('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budgets->shouldReceive('createOrFind')->with('')->andReturn(null);
$budgets->shouldReceive('find')->andReturn(null);
// call
$this->call('POST', '/transactions/add/transfer', $data);
$this->call('POST', '/transactions/store/transfer', $data);
// test
$this->assertRedirectedToRoute('index');
@@ -273,7 +293,7 @@ class TransactionControllerTest extends TestCase
$tj->shouldReceive('createSimpleJournal')->once()->andReturn($journal);
// call
$this->call('POST', '/transactions/add/withdrawal', $data);
$this->call('POST', '/transactions/store/withdrawal', $data);
// test
$this->assertRedirectedToRoute('index');
@@ -320,7 +340,7 @@ class TransactionControllerTest extends TestCase
$tj->shouldReceive('createSimpleJournal')->once()->andReturn($journal);
// call
$this->call('POST', '/transactions/add/deposit', $data);
$this->call('POST', '/transactions/store/deposit', $data);
// test
$this->assertRedirectedToRoute('index');
@@ -370,10 +390,10 @@ class TransactionControllerTest extends TestCase
$tj->shouldReceive('createSimpleJournal')->andThrow('Firefly\Exception\FireflyException');
// call
$this->call('POST', '/transactions/add/withdrawal', $data);
$this->call('POST', '/transactions/store/withdrawal', $data);
// test
$this->assertRedirectedToRoute('transactions.withdrawal');
$this->assertRedirectedToRoute('transactions.create',['what' => 'withdrawal']);
}
/**
@@ -420,10 +440,10 @@ class TransactionControllerTest extends TestCase
$tj->shouldReceive('createSimpleJournal')->andThrow('Firefly\Exception\FireflyException');
// call
$this->call('POST', '/transactions/add/deposit', $data);
$this->call('POST', '/transactions/store/deposit', $data);
// test
$this->assertRedirectedToRoute('transactions.deposit');
$this->assertRedirectedToRoute('transactions.create',['what' => 'deposit']);
}
/**
@@ -455,15 +475,20 @@ class TransactionControllerTest extends TestCase
$categories = $this->mock('Firefly\Storage\Category\CategoryRepositoryInterface');
$categories->shouldReceive('createOrFind')->with($category->name)->andReturn($category);
// mock budget repository
$budgets = $this->mock('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budgets->shouldReceive('createOrFind')->with('')->andReturn(null);
$budgets->shouldReceive('find')->andReturn(null);
// mock transaction journal:
$tj = $this->mock('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$tj->shouldReceive('createSimpleJournal')->andThrow('Firefly\Exception\FireflyException');
// call
$this->call('POST', '/transactions/add/transfer', $data);
$this->call('POST', '/transactions/store/transfer', $data);
// test
$this->assertRedirectedToRoute('transactions.transfer');
$this->assertRedirectedToRoute('transactions.create',['what' => 'transfer']);
}
public function tearDown()

View File

@@ -18,10 +18,9 @@
<button name="range" value="3M" class="btn btn-default @if($r=='3M') btn-info @endif btn-sm" type="submit">3M</button>
<button name="range" value="6M" class="btn btn-default @if($r=='6M') btn-info @endif btn-sm" type="submit">6M</button>
</span>
<input value="{{Session::get('start')->format('Y-m-d')}}" name="start" type="date" style="width:15%;" class="form-control input-sm">
<input value="{{Session::get('end')->format('Y-m-d')}}" name="end" type="date" style="width:15%;" class="form-control input-sm">
<input value="{{Session::get('start')->format('Y-m-d')}}" name="start" type="date" style="width:15%;border-right:0;" class="form-control input-sm">
<input value="{{Session::get('end')->format('Y-m-d')}}" name="end" type="date" style="width:15%;border-right:0;" class="form-control input-sm">
<button class="btn btn-default btn-sm @if($r=='custom') btn-info @endif" type="submit" name="range" value="custom">Custom</button>
</div>
</form>

View File

@@ -4,9 +4,9 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Add... <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{{route('transactions.withdrawal')}}" title="For when you spend money"><span class="glyphicon glyphicon-arrow-left"></span> Withdrawal</a></li>
<li><a href="{{route('transactions.deposit')}}" title="For when you earn money"><span class="glyphicon glyphicon-arrow-right"></span> Deposit</a></li>
<li><a href="{{route('transactions.transfer')}}" title="For when you move money around"><span class="glyphicon glyphicon-resize-full"></span> Transfer</a></li>
<li><a href="{{route('transactions.create','withdrawal')}}" title="For when you spend money"><span class="glyphicon glyphicon-arrow-left"></span> Withdrawal</a></li>
<li><a href="{{route('transactions.create','deposit')}}" title="For when you earn money"><span class="glyphicon glyphicon-arrow-right"></span> Deposit</a></li>
<li><a href="{{route('transactions.create','transfer')}}" title="For when you move money around"><span class="glyphicon glyphicon-resize-full"></span> Transfer</a></li>
</ul>
</li>
</ul>

View File

@@ -0,0 +1,164 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Add a new {{$what}}</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<p class="text-info">
Technically speaking, withdrawals, deposits and transfers are all transactions, moving money from
account <em>A</em> to account <em>B</em>.
</p>
<p class="text-info">
@if($what == 'withdrawal')
A withdrawal is when you spend money on something, moving an amount to a <em>beneficiary</em>.
@endif
@if($what == 'deposit')
A deposit is when you earn money, moving an amount from a beneficiary into your own account.
@endif
@if($what == 'transfer')
TRANSFER
@endif
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('transactions.store',$what)])}}
<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" />
</div>
</div>
<!-- SHOW ACCOUNT (FROM) ONLY FOR WITHDRAWALS AND DEPOSITS -->
@if($what == 'deposit' || $what == 'withdrawal')
<div class="form-group">
<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">
{{Form::select('account_id',$accounts,Input::old('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')}}}" 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_from_id" class="col-sm-4 control-label">Account from</label>
<div class="col-sm-8">
{{Form::select('account_to_id',$accounts,Input::old('account_from_id'),['class' => 'form-control'])}}
</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_from_id',$accounts,Input::old('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') or 0}}" 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>
<!-- 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="Create {{$what}}" class="btn btn-info" />
</div>
</div>
</div>
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Optional fields</h4>
<!-- 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'])}}
<span class="help-block">Select one of your budgets to make this transaction a part of it.</span>
</div>
</div>
@endif
<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" />
<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.
</span>
</div>
</div>
</div>
@stop
@section('scripts')
<script type="text/javascript" src="assets/javascript/withdrawal.js"></script>
@stop

View File

@@ -0,0 +1,98 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Edit transaction ""</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<p class="text-info">
Technically speaking, withdrawals, deposits and transfers are all transactions, moving money from
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.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal'])}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Mandatory fields</h4>
<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" />
</div>
</div>
<div class="form-group">
<label for="beneficiary" class="col-sm-4 control-label">Beneficiary (payer)</label>
<div class="col-sm-8">
<input type="text" name="beneficiary" value="{{{Input::old('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>
<div class="form-group">
<label for="account_id" class="col-sm-4 control-label">Account</label>
<div class="col-sm-8">
{{Form::select('account_id',$accounts,Input::old('account_id'),['class' => 'form-control'])}}
</div>
</div>
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">Amount spent</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" />
</div>
</div>
<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" />
</div>
</div>
</div>
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Optional fields</h4>
<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" />
<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.
</span>
</div>
</div>
</div>
@stop
@section('scripts')
<script type="text/javascript" src="assets/javascript/withdrawal.js"></script>
@stop

View File

@@ -20,7 +20,7 @@
@endif
</td>
<td><a href="#">{{{$journal->description}}}</a></td>
<td><a href="{{route('transactions.show',$journal->id)}}">{{{$journal->description}}}</a></td>
<td>{{$journal->date->format('jS M Y')}}</td>
<td>
@foreach($journal->transactions as $t)

View File

@@ -0,0 +1,77 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<h1>Firefly
<small>Transaction "{{{$journal->description}}}"</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<h3>Metadata</h3>
<table class="table">
<tr>
<td>Date</td>
<td>{{{$journal->date->format('jS F Y')}}}</td>
</tr>
<tr>
<td>Currency</td>
<td>{{{$journal->transactioncurrency->code}}}</td>
</tr>
<tr>
<td>Type</td>
<td>{{{$journal->transactiontype->type}}}</td>
</tr>
<tr>
<td>Completed</td>
<td>
@if($journal->completed == 1)
<span class="text-success">Yes</span>
@else
<span class="text-danger">No</span>
@endif
</td>
</tr>
@foreach($journal->components as $component)
<tr>
<td>{{$component->class}}</td>
<td>{{{$component->name}}}</td>
</tr>
@endforeach
</table>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<h3>Transactions</h3>
@foreach($journal->transactions as $t)
<h4>{{{$t->account->name}}}<br /><small>{{{$t->account->accounttype->description}}}</small></h4>
<table class="table">
<tr>
<td>Amount</td>
<td>{{mf($t->amount)}}</td>
</tr>
@if(!is_null($t->description))
<tr>
<td>Description</td>
<td>{{{$t->description}}}</td>
</tr>
@endif
</table>
@endforeach
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<div class="btn-group">
<a class="btn btn-default" href="{{route('transactions.edit',$journal->id)}}"><span class="glyphicon glyphicon-pencil"></span> Edit</a> <a href="{{route('transactions.delete',$journal->id)}}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> Delete</a>
</div>
</div>
</div>
@stop
@section('scripts')
@stop

View File

@@ -16,16 +16,37 @@ $(function () {
title: {
text: obj.data('title')
},
yAxis: {
title: {
text: 'Balance (€)'
},
formatter: function () {
return '$' + Highcharts.numberFormat(this.y, 0);
}
},
xAxis: {
type: 'datetime'
floor: 0,
type: 'datetime',
dateTimeLabelFormats: {
month: '%e %b',
year: '%b'
},
title: {
text: 'Date'
}
},
tooltip: {
valuePrefix: '€ '
valuePrefix: '€ ',
formatter: function () {
return '€ ' + Highcharts.numberFormat(this.y, 2);
}
},
plotOptions: {
line: {
negativeColor: '#FF0000',
threshold: 0,
lineWidth: 1,
marker: {
radius: 2