diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index f5139cb617..08685fdee9 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -1,6 +1,7 @@ accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')->orderBy('name', 'ASC')->where( - 'active', 1 - )->orderBy('name', 'DESC')->get(['accounts.*']) - ); + $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); $budgets[0] = '(no budget)'; $piggies = ExpandedForm::makeSelectList(Auth::user()->piggyBanks()->get()); @@ -99,11 +96,11 @@ class TransactionController extends Controller * * @return \Illuminate\Http\RedirectResponse */ - public function destroy(TransactionJournal $transactionJournal) + public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal) { Session::flash('success', 'Transaction "' . e($transactionJournal->description) . '" destroyed.'); - $transactionJournal->delete(); + $repository->delete($transactionJournal); // redirect to previous URL: return Redirect::to(Session::get('transactions.delete.url')); @@ -116,14 +113,10 @@ class TransactionController extends Controller * * @return $this */ - public function edit(TransactionJournal $journal) + public function edit(AccountRepositoryInterface $repository, TransactionJournal $journal) { $what = strtolower($journal->transactiontype->type); - $accounts = ExpandedForm::makeSelectList( - Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')->where('active', 1)->orderBy( - 'name', 'DESC' - )->get(['accounts.*']) - ); + $accounts = ExpandedForm::makeSelectList($repository->getAccounts(['Default account', 'Asset account'])); $budgets = ExpandedForm::makeSelectList(Auth::user()->budgets()->get()); $budgets[0] = '(no budget)'; $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) { case 'expenses': case 'withdrawal': @@ -203,18 +198,10 @@ class TransactionController extends Controller break; } - $page = intval(\Input::get('page')); - $offset = $page > 0 ? ($page - 1) * 50 : 0; + $page = intval(Input::get('page')); + $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); 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) { $order = 0; foreach ($ids as $id) { - $journal = Auth::user()->transactionjournals()->where('id', $id)->where('date', Input::get('date'))->first(); + + $journal = $repository->getWithDate($id, $date); if ($journal) { $journal->order = $order; $order++; @@ -248,19 +239,11 @@ class TransactionController extends Controller * * @return $this */ - public function show(TransactionJournal $journal) + public function show(JournalRepositoryInterface $repository, TransactionJournal $journal) { $journal->transactions->each( - function (Transaction $t) use ($journal) { - $t->before = floatval( - $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') - ); + function (Transaction $t) use ($journal, $repository) { + $t->before = $repository->getAmountBefore($journal, $t); $t->after = $t->before + $t->amount; } ); diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 9a4ab7d52f..f1291545ae 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -4,6 +4,7 @@ namespace FireflyIII\Repositories\Journal; use App; use Auth; +use Carbon\Carbon; use DB; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; @@ -13,6 +14,7 @@ use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Log; @@ -24,6 +26,23 @@ use Log; 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 * @@ -34,6 +53,25 @@ class JournalRepository implements JournalRepositoryInterface 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 * @@ -44,6 +82,28 @@ class JournalRepository implements JournalRepositoryInterface 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 * @@ -54,6 +114,17 @@ class JournalRepository implements JournalRepositoryInterface 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. diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 89e15791fb..6bb340283b 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -2,9 +2,11 @@ namespace FireflyIII\Repositories\Journal; +use Carbon\Carbon; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; /** @@ -14,6 +16,13 @@ use Illuminate\Support\Collection; */ interface JournalRepositoryInterface { + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function delete(TransactionJournal $journal); + /** * Get users first transaction journal * @@ -21,6 +30,22 @@ interface JournalRepositoryInterface */ 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 * @@ -28,6 +53,15 @@ interface JournalRepositoryInterface */ 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 * diff --git a/tests/controllers/TransactionControllerTest.php b/tests/controllers/TransactionControllerTest.php new file mode 100644 index 0000000000..aa79e1dc49 --- /dev/null +++ b/tests/controllers/TransactionControllerTest.php @@ -0,0 +1,238 @@ +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(); + } + +} \ No newline at end of file diff --git a/tests/factories/all.php b/tests/factories/all.php index 133d8adf77..d00a36ea9a 100644 --- a/tests/factories/all.php +++ b/tests/factories/all.php @@ -170,8 +170,11 @@ FactoryMuffin::define( 'type' => function () { $types = ['Expense account', 'Revenue account', 'Asset account']; $count = DB::table('account_types')->count(); - - return $types[$count]; + if ($count < 3) { + return $types[$count]; + } else { + return RandomString::generateRandomString(10); + } }, '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( 'FireflyIII\Models\TransactionType', [