Partial coverage of the transaction controller.

This commit is contained in:
James Cole 2015-05-04 23:46:14 +02:00
parent eb090f7265
commit 3176e54614
5 changed files with 392 additions and 50 deletions

View File

@ -1,6 +1,7 @@
<?php namespace FireflyIII\Http\Controllers; <?php namespace FireflyIII\Http\Controllers;
use Auth; use Auth;
use Carbon\Carbon;
use ExpandedForm; use ExpandedForm;
use FireflyIII\Events\JournalCreated; use FireflyIII\Events\JournalCreated;
use FireflyIII\Events\JournalSaved; use FireflyIII\Events\JournalSaved;
@ -8,9 +9,10 @@ use FireflyIII\Http\Requests;
use FireflyIII\Http\Requests\JournalFormRequest; use FireflyIII\Http\Requests\JournalFormRequest;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Pagination\LengthAwarePaginator;
use Input; use Input;
use Log;
use Redirect; use Redirect;
use Response; use Response;
use Session; use Session;
@ -34,19 +36,14 @@ class TransactionController extends Controller
} }
/** /**
* Shows the view helping the user to create a new transaction journal. * @param AccountRepositoryInterface $repository
* @param string $what
* *
* @param string $what * @return View
*
* @return \Illuminate\View\View
*/ */
public function create($what = 'deposit') public function create(AccountRepositoryInterface $repository, $what = 'deposit')
{ {
$accounts = ExpandedForm::makeSelectList( $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account']));
Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')->orderBy('name', 'ASC')->where(
'active', 1
)->orderBy('name', 'DESC')->get(['accounts.*'])
);
$budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get());
$budgets[0] = '(no budget)'; $budgets[0] = '(no budget)';
$piggies = ExpandedForm::makeSelectList(Auth::user()->piggyBanks()->get()); $piggies = ExpandedForm::makeSelectList(Auth::user()->piggyBanks()->get());
@ -99,11 +96,11 @@ class TransactionController extends Controller
* *
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(TransactionJournal $transactionJournal) public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal)
{ {
Session::flash('success', 'Transaction "' . e($transactionJournal->description) . '" destroyed.'); Session::flash('success', 'Transaction "' . e($transactionJournal->description) . '" destroyed.');
$transactionJournal->delete(); $repository->delete($transactionJournal);
// redirect to previous URL: // redirect to previous URL:
return Redirect::to(Session::get('transactions.delete.url')); return Redirect::to(Session::get('transactions.delete.url'));
@ -116,14 +113,10 @@ class TransactionController extends Controller
* *
* @return $this * @return $this
*/ */
public function edit(TransactionJournal $journal) public function edit(AccountRepositoryInterface $repository, TransactionJournal $journal)
{ {
$what = strtolower($journal->transactiontype->type); $what = strtolower($journal->transactiontype->type);
$accounts = ExpandedForm::makeSelectList( $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account']));
Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')->where('active', 1)->orderBy(
'name', 'DESC'
)->get(['accounts.*'])
);
$budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get());
$budgets[0] = '(no budget)'; $budgets[0] = '(no budget)';
$transactions = $journal->transactions()->orderBy('amount', 'DESC')->get(); $transactions = $journal->transactions()->orderBy('amount', 'DESC')->get();
@ -176,12 +169,14 @@ class TransactionController extends Controller
} }
/** /**
* @param $what * @param JournalRepositoryInterface $repository
* @param $what
* *
* @return $this * @return View
*/ */
public function index($what) public function index(JournalRepositoryInterface $repository, $what)
{ {
$types = [];
switch ($what) { switch ($what) {
case 'expenses': case 'expenses':
case 'withdrawal': case 'withdrawal':
@ -203,18 +198,10 @@ class TransactionController extends Controller
break; break;
} }
$page = intval(\Input::get('page')); $page = intval(Input::get('page'));
$offset = $page > 0 ? ($page - 1) * 50 : 0; $offset = $page > 0 ? ($page - 1) * 50 : 0;
$journals = $repository->getJournalsOfTypes($types, $offset, $page);
$set = Auth::user()->transactionJournals()->transactionTypes($types)->withRelevantData()->take(50)->offset($offset)
->orderBy('date', 'DESC')
->orderBy('order', 'ASC')
->orderBy('id', 'DESC')
->get(
['transaction_journals.*']
);
$count = Auth::user()->transactionJournals()->transactionTypes($types)->count();
$journals = new LengthAwarePaginator($set, $count, 50, $page);
$journals->setPath('transactions/' . $what); $journals->setPath('transactions/' . $what);
return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals')); return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals'));
@ -222,15 +209,19 @@ class TransactionController extends Controller
} }
/** /**
* Reorder transactions (which all must have the same date) * @param JournalRepositoryInterface $repository
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
public function reorder() public function reorder(JournalRepositoryInterface $repository)
{ {
$ids = Input::get('items'); $ids = Input::get('items');
$date = new Carbon(Input::get('date'));
if (count($ids) > 0) { if (count($ids) > 0) {
$order = 0; $order = 0;
foreach ($ids as $id) { foreach ($ids as $id) {
$journal = Auth::user()->transactionjournals()->where('id', $id)->where('date', Input::get('date'))->first();
$journal = $repository->getWithDate($id, $date);
if ($journal) { if ($journal) {
$journal->order = $order; $journal->order = $order;
$order++; $order++;
@ -248,19 +239,11 @@ class TransactionController extends Controller
* *
* @return $this * @return $this
*/ */
public function show(TransactionJournal $journal) public function show(JournalRepositoryInterface $repository, TransactionJournal $journal)
{ {
$journal->transactions->each( $journal->transactions->each(
function (Transaction $t) use ($journal) { function (Transaction $t) use ($journal, $repository) {
$t->before = floatval( $t->before = $repository->getAmountBefore($journal, $t);
$t->account->transactions()->leftJoin(
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
)
->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d'))
->where('transaction_journals.order', '>=', $journal->order)
->where('transaction_journals.id', '!=', $journal->id)
->sum('transactions.amount')
);
$t->after = $t->before + $t->amount; $t->after = $t->before + $t->amount;
} }
); );

View File

@ -4,6 +4,7 @@ namespace FireflyIII\Repositories\Journal;
use App; use App;
use Auth; use Auth;
use Carbon\Carbon;
use DB; use DB;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
@ -13,6 +14,7 @@ use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
@ -24,6 +26,23 @@ use Log;
class JournalRepository implements JournalRepositoryInterface class JournalRepository implements JournalRepositoryInterface
{ {
/**
* @param TransactionJournal $journal
*
* @return bool
*/
public function delete(TransactionJournal $journal)
{
// delete transactions first:
/** @var Transaction $transaction */
foreach ($journal->transactions()->get() as $transaction) {
$transaction->delete();
}
$journal->delete();
return true;
}
/** /**
* Get users first transaction journal * Get users first transaction journal
* *
@ -34,6 +53,25 @@ class JournalRepository implements JournalRepositoryInterface
return Auth::user()->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); return Auth::user()->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']);
} }
/**
* @param TransactionJournal $journal
* @param Transaction $transaction
*
* @return float
*/
public function getAmountBefore(TransactionJournal $journal, Transaction $transaction)
{
return floatval(
$transaction->account->transactions()->leftJoin(
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
)
->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d'))
->where('transaction_journals.order', '>=', $journal->order)
->where('transaction_journals.id', '!=', $journal->id)
->sum('transactions.amount')
);
}
/** /**
* @param TransactionType $dbType * @param TransactionType $dbType
* *
@ -44,6 +82,28 @@ class JournalRepository implements JournalRepositoryInterface
return Auth::user()->transactionjournals()->where('transaction_type_id', $dbType->id)->orderBy('id', 'DESC')->take(50)->get(); return Auth::user()->transactionjournals()->where('transaction_type_id', $dbType->id)->orderBy('id', 'DESC')->take(50)->get();
} }
/**
* @param array $types
* @param int $offset
* @param int $page
*
* @return LengthAwarePaginator
*/
public function getJournalsOfTypes(array $types, $offset, $page)
{
$set = Auth::user()->transactionJournals()->transactionTypes($types)->withRelevantData()->take(50)->offset($offset)
->orderBy('date', 'DESC')
->orderBy('order', 'ASC')
->orderBy('id', 'DESC')
->get(
['transaction_journals.*']
);
$count = Auth::user()->transactionJournals()->transactionTypes($types)->count();
$journals = new LengthAwarePaginator($set, $count, 50, $page);
return $journals;
}
/** /**
* @param $type * @param $type
* *
@ -54,6 +114,17 @@ class JournalRepository implements JournalRepositoryInterface
return TransactionType::whereType($type)->first(); return TransactionType::whereType($type)->first();
} }
/**
* @param $id
* @param Carbon $date
*
* @return TransactionJournal
*/
public function getWithDate($id, Carbon $date)
{
return Auth::user()->transactionjournals()->where('id', $id)->where('date', $date->format('Y-m-d'))->first();
}
/** /**
* *
* * Remember: a balancingAct takes at most one expense and one transfer. * * Remember: a balancingAct takes at most one expense and one transfer.

View File

@ -2,9 +2,11 @@
namespace FireflyIII\Repositories\Journal; namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
@ -14,6 +16,13 @@ use Illuminate\Support\Collection;
*/ */
interface JournalRepositoryInterface interface JournalRepositoryInterface
{ {
/**
* @param TransactionJournal $journal
*
* @return bool
*/
public function delete(TransactionJournal $journal);
/** /**
* Get users first transaction journal * Get users first transaction journal
* *
@ -21,6 +30,22 @@ interface JournalRepositoryInterface
*/ */
public function first(); public function first();
/**
* @param $id
* @param Carbon $date
*
* @return TransactionJournal
*/
public function getWithDate($id, Carbon $date);
/**
* @param TransactionJournal $journal
* @param Transaction $transaction
*
* @return float
*/
public function getAmountBefore(TransactionJournal $journal, Transaction $transaction);
/** /**
* @param TransactionType $dbType * @param TransactionType $dbType
* *
@ -28,6 +53,15 @@ interface JournalRepositoryInterface
*/ */
public function getJournalsOfType(TransactionType $dbType); public function getJournalsOfType(TransactionType $dbType);
/**
* @param array $types
* @param int $offset
* @param int $page
*
* @return LengthAwarePaginator
*/
public function getJournalsOfTypes(array $types, $offset, $page);
/** /**
* @param $type * @param $type
* *

View File

@ -0,0 +1,238 @@
<?php
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use League\FactoryMuffin\Facade as FactoryMuffin;
/**
* Class TransactionControllerTest
*/
class TransactionControllerTest extends TestCase
{
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
public function setUp()
{
parent::setUp();
FactoryMuffin::create('FireflyIII\User');
}
/**
* This method is called before the first test of this test class is run.
*
* @since Method available since Release 3.4.0
*/
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
public function tearDown()
{
parent::tearDown();
}
public function testCreate()
{
$user = FactoryMuffin::create('FireflyIII\User');
$this->be($user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface');
// fake!
$repository->shouldReceive('getAccounts')->andReturn(new Collection);
$this->call('GET', '/transactions/create/withdrawal?account_id=12');
$this->assertResponseOk();
}
public function testDelete()
{
$journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal');
$this->be($journal->user);
$this->call('GET', '/transaction/delete/' . $journal->id);
$this->assertResponseOk();
}
public function testDestroy()
{
$journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal');
$this->be($journal->user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface');
// fake!
$repository->shouldReceive('delete')->andReturn(true);
$this->call('POST', '/transaction/destroy/' . $journal->id, ['_token' => 'replaceMe']);
$this->assertResponseStatus(302);
$this->assertSessionHas('success');
}
public function testEdit()
{
// make complete journal:
$accountType = FactoryMuffin::create('FireflyIII\Models\AccountType');
$journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal');
$account = FactoryMuffin::create('FireflyIII\Models\Account');
$transaction1 = FactoryMuffin::create('FireflyIII\Models\Transaction');
$transaction2 = FactoryMuffin::create('FireflyIII\Models\Transaction');
$accountType->type = 'Asset account';
$account->account_type_id = $accountType->id;
$account->save();
$transaction1->account_id = $account->id;
$transaction1->transaction_journal_id = $journal->id;
$transaction1->save();
$transaction2->account_id = $account->id;
$transaction2->transaction_journal_id = $journal->id;
$transaction2->save();
// also add some tags:
$tag = FactoryMuffin::create('FireflyIII\Models\Tag');
$tag->transactionJournals()->save($journal);
// and a category and a budget:
$budget = FactoryMuffin::create('FireflyIII\Models\Budget');
$category = FactoryMuffin::create('FireflyIII\Models\Category');
$category->transactionJournals()->save($journal);
$budget->transactionJournals()->save($journal);
// and a piggy bank event:
$pbEvent = FactoryMuffin::create('FireflyIII\Models\PiggyBankEvent');
$pbEvent->transaction_journal_id = $journal->id;
$pbEvent->save();
$this->be($journal->user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface');
// fake!
$repository->shouldReceive('getAccounts')->andReturn(new Collection);
$this->call('GET', '/transaction/edit/' . $journal->id);
$this->assertResponseOk();
}
public function testIndexRevenue()
{
$user = FactoryMuffin::create('FireflyIII\User');
$this->be($user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface');
// fake!
$repository->shouldReceive('getJournalsOfTypes')->withArgs([['Deposit'], 0, 0])->andReturn(new LengthAwarePaginator(new Collection, 0, 50));
$this->call('GET', '/transactions/deposit');
$this->assertResponseOk();
}
public function testIndexTransfer()
{
$user = FactoryMuffin::create('FireflyIII\User');
$this->be($user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface');
// fake!
$repository->shouldReceive('getJournalsOfTypes')->withArgs([['Transfer'], 0, 0])->andReturn(new LengthAwarePaginator(new Collection, 0, 50));
$this->call('GET', '/transactions/transfers');
$this->assertResponseOk();
}
public function testIndexWithdrawal()
{
$user = FactoryMuffin::create('FireflyIII\User');
$this->be($user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface');
// fake!
$repository->shouldReceive('getJournalsOfTypes')->withArgs([['Withdrawal'], 0, 0])->andReturn(new LengthAwarePaginator(new Collection, 0, 50));
$this->call('GET', '/transactions/withdrawal');
$this->assertResponseOk();
}
public function testReorder()
{
$journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal');
$this->be($journal->user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface');
// fake!
$repository->shouldReceive('getWithDate')->withAnyArgs()->andReturn($journal);
$data = [
'items' => [$journal->id],
'date' => $journal->date->format('Y-m-d'),
'_token' => 'replaceMe'
];
$this->call('POST', '/transaction/reorder', $data);
$this->assertResponseOk();
}
public function testShow()
{
$journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal');
$transaction1 = FactoryMuffin::create('FireflyIII\Models\Transaction');
$currency = FactoryMuffin::create('FireflyIII\Models\TransactionCurrency');
$transaction1->transaction_journal_id = $journal->id;
$transaction1->save();
$this->be($journal->user);
// mock!
$repository = $this->mock('FireflyIII\Repositories\Journal\JournalRepositoryInterface');
// fake!
$repository->shouldReceive('getAmountBefore')->withAnyArgs()->andReturn(5);
Amount::shouldReceive('getDefaultCurrency')->once()->andReturn($currency);
Amount::shouldReceive('getAllCurrencies')->once()->andReturn([$currency]);
Amount::shouldReceive('getCurrencyCode')->andReturn('X');
Amount::shouldReceive('formatTransaction')->andReturn('X');
Amount::shouldReceive('format')->andReturn('X');
$this->call('GET', '/transaction/show/' . $journal->id);
$this->assertResponseOk();
}
public function testStore()
{
$this->markTestIncomplete();
}
public function testUpdate()
{
$this->markTestIncomplete();
}
}

View File

@ -170,8 +170,11 @@ FactoryMuffin::define(
'type' => function () { 'type' => function () {
$types = ['Expense account', 'Revenue account', 'Asset account']; $types = ['Expense account', 'Revenue account', 'Asset account'];
$count = DB::table('account_types')->count(); $count = DB::table('account_types')->count();
if ($count < 3) {
return $types[$count]; return $types[$count];
} else {
return RandomString::generateRandomString(10);
}
}, },
'editable' => 1, 'editable' => 1,
] ]
@ -224,6 +227,19 @@ FactoryMuffin::define(
] ]
); );
FactoryMuffin::define(
'FireflyIII\Models\PiggyBankEvent',
[
'piggy_bank_id' => 'factory|FireflyIII\Models\PiggyBank',
'transaction_journal_id' => 'factory|FireflyIII\Models\TransactionJournal',
'date' => 'date',
'amount' => function () {
return rand(1, 100);
},
]
);
FactoryMuffin::define( FactoryMuffin::define(
'FireflyIII\Models\TransactionType', 'FireflyIII\Models\TransactionType',
[ [