diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 1535577102..6a78a4718f 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -1,6 +1,6 @@ match); $description = []; $expense = null; // get users expense accounts: - $type = AccountType::where('type', 'Expense account')->first(); - $accounts = Auth::user()->accounts()->where('account_type_id', $type->id)->get(); + $accounts = $repository->getAccounts(Config::get('firefly.accountTypesByIdentifier.expense'), -1); foreach ($matches as $match) { $match = strtolower($match); @@ -81,7 +81,7 @@ class BillController extends Controller */ public function create() { - $periods = \Config::get('firefly.periods_to_text'); + $periods = Config::get('firefly.periods_to_text'); return view('bills.create')->with('periods', $periods)->with('subTitle', 'Create new'); } @@ -101,9 +101,10 @@ class BillController extends Controller * * @return \Illuminate\Http\RedirectResponse */ - public function destroy(Bill $bill) + public function destroy(Bill $bill, BillRepositoryInterface $repository) { - $bill->delete(); + $repository->destroy($bill); + Session::flash('success', 'The bill was deleted.'); return Redirect::route('bills.index'); @@ -117,7 +118,7 @@ class BillController extends Controller */ public function edit(Bill $bill) { - $periods = \Config::get('firefly.periods_to_text'); + $periods = Config::get('firefly.periods_to_text'); return view('bills.edit')->with('periods', $periods)->with('bill', $bill)->with('subTitle', 'Edit "' . e($bill->name) . '"'); } @@ -129,15 +130,11 @@ class BillController extends Controller */ public function index(BillRepositoryInterface $repository) { - $bills = Auth::user()->bills()->orderBy('name', 'ASC')->get(); + $bills = $repository->getBills(); $bills->each( function (Bill $bill) use ($repository) { $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill); - $last = $bill->transactionjournals()->orderBy('date', 'DESC')->first(); - $bill->lastFoundMatch = null; - if ($last) { - $bill->lastFoundMatch = $last->date; - } + $bill->lastFoundMatch = $repository->lastFoundMatch($bill); } ); @@ -154,25 +151,15 @@ class BillController extends Controller if (intval($bill->active) == 0) { Session::flash('warning', 'Inactive bills cannot be scanned.'); - return Redirect::intended('/'); + return Redirect::to(URL::previous()); } - $set = \DB::table('transactions')->where('amount', '>', 0)->where('amount', '>=', $bill->amount_min)->where('amount', '<=', $bill->amount_max)->get( - ['transaction_journal_id'] - ); - $ids = []; + $journals = $repository->getPossiblyRelatedJournals($bill); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $repository->scan($bill, $journal); + } - /** @var Transaction $entry */ - foreach ($set as $entry) { - $ids[] = intval($entry->transaction_journal_id); - } - if (count($ids) > 0) { - $journals = Auth::user()->transactionjournals()->whereIn('id', $ids)->get(); - /** @var TransactionJournal $journal */ - foreach ($journals as $journal) { - $repository->scan($bill, $journal); - } - } Session::flash('success', 'Rescanned everything.'); @@ -186,15 +173,10 @@ class BillController extends Controller */ public function show(Bill $bill, BillRepositoryInterface $repository) { - $journals = $bill->transactionjournals()->withRelevantData() - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->get(); + $journals = $repository->getJournals($bill); $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill); $hideBill = true; - return view('bills.show', compact('journals', 'hideBill', 'bill'))->with('subTitle', e($bill->name)); } diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index e8300109ad..4d850ef7bb 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -88,7 +88,7 @@ class TransactionController extends Controller // put previous url in session Session::put('transactions.delete.url', URL::previous()); - return View::make('transactions.delete', compact('journal', 'subTitle')); + return view('transactions.delete', compact('journal', 'subTitle')); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index d39c9b65b2..b09f53bdae 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -59,14 +59,20 @@ class AccountRepository implements AccountRepositoryInterface */ public function getAccounts(array $types, $page) { - $size = 50; - $offset = ($page - 1) * $size; - - return Auth::user()->accounts()->with( + $query = Auth::user()->accounts()->with( ['accountmeta' => function (HasMany $query) { $query->where('name', 'accountRole'); }] - )->accountTypeIn($types)->take($size)->offset($offset)->orderBy('accounts.name', 'ASC')->get(['accounts.*']); + )->accountTypeIn($types)->orderBy('accounts.name', 'ASC'); + + if ($page == -1) { + return $query->get(['accounts.*']); + } else { + $size = 50; + $offset = ($page - 1) * $size; + + return $query->take($size)->offset($offset)->get(['accounts.*']); + } } /** diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 9f928cabb4..049634ac40 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -2,9 +2,12 @@ namespace FireflyIII\Repositories\Bill; +use Auth; use Carbon\Carbon; use FireflyIII\Models\Bill; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; +use Illuminate\Support\Collection; use Log; use Navigation; @@ -15,6 +18,62 @@ use Navigation; */ class BillRepository implements BillRepositoryInterface { + /** + * @param Bill $bill + * + * @return mixed + */ + public function destroy(Bill $bill) + { + return $bill->delete(); + } + + /** + * @return Collection + */ + public function getBills() + { + return Auth::user()->bills()->orderBy('name', 'ASC')->get(); + } + + /** + * @param Bill $bill + * + * @return Collection + */ + public function getJournals(Bill $bill) + { + return $bill->transactionjournals()->withRelevantData() + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->get(); + } + + /** + * @param Bill $bill + * + * @return Collection + */ + public function getPossiblyRelatedJournals(Bill $bill) + { + $set = \DB::table('transactions')->where('amount', '>', 0)->where('amount', '>=', $bill->amount_min)->where('amount', '<=', $bill->amount_max)->get( + ['transaction_journal_id'] + ); + $ids = []; + + /** @var Transaction $entry */ + foreach ($set as $entry) { + $ids[] = intval($entry->transaction_journal_id); + } + $journals = new Collection; + if (count($ids) > 0) { + $journals = Auth::user()->transactionjournals()->whereIn('id', $ids)->get(); + } + + return $journals; + } + /** * Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself) * and returns date ranges that fall within the given range; those ranges are the bills expected. When a bill is due on the 14th of the month and @@ -58,6 +117,21 @@ class BillRepository implements BillRepositoryInterface return $validRanges; } + /** + * @param Bill $bill + * + * @return Carbon|null + */ + public function lastFoundMatch(Bill $bill) + { + $last = $bill->transactionjournals()->orderBy('date', 'DESC')->first(); + if ($last) { + return $last->date; + } + + return null; + } + /** * @param Bill $bill * diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 34142fcc37..134d275f00 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -5,6 +5,7 @@ namespace FireflyIII\Repositories\Bill; use Carbon\Carbon; use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionJournal; +use Illuminate\Support\Collection; /** * Interface BillRepositoryInterface @@ -17,9 +18,28 @@ interface BillRepositoryInterface /** * @param Bill $bill * - * @return Carbon|null + * @return mixed */ - public function nextExpectedMatch(Bill $bill); + public function destroy(Bill $bill); + + /** + * @return Collection + */ + public function getBills(); + + /** + * @param Bill $bill + * + * @return Collection + */ + public function getPossiblyRelatedJournals(Bill $bill); + + /** + * @param Bill $bill + * + * @return Collection + */ + public function getJournals(Bill $bill); /** * Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself) @@ -34,6 +54,28 @@ interface BillRepositoryInterface */ public function getRanges(Bill $bill, Carbon $start, Carbon $end); + /** + * @param Bill $bill + * + * @return Carbon|null + */ + public function lastFoundMatch(Bill $bill); + + /** + * @param Bill $bill + * + * @return Carbon|null + */ + public function nextExpectedMatch(Bill $bill); + + /** + * @param Bill $bill + * @param TransactionJournal $journal + * + * @return bool + */ + public function scan(Bill $bill, TransactionJournal $journal); + /** * @param array $data * @@ -49,12 +91,4 @@ interface BillRepositoryInterface */ public function update(Bill $bill, array $data); - /** - * @param Bill $bill - * @param TransactionJournal $journal - * - * @return bool - */ - public function scan(Bill $bill, TransactionJournal $journal); - } diff --git a/tests/controllers/BillControllerTest.php b/tests/controllers/BillControllerTest.php new file mode 100644 index 0000000000..aa24ff040d --- /dev/null +++ b/tests/controllers/BillControllerTest.php @@ -0,0 +1,249 @@ +be($bill->user); + + // create an expense account: + $expense = FactoryMuffin::create('FireflyIII\Models\Account'); + // fix the name of the expense account to match one of the words + // in the bill: + $words = explode(',', $bill->match); + $word = $words[1]; + $expense->name = $word; + $expense->save(); + + // mock repository: + $repository = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository->shouldReceive('getAccounts')->andReturn([$expense]); + + // go! + $this->call('GET', '/bills/add/' . $bill->id); + $this->assertSessionHas('preFilled'); + $this->assertRedirectedToRoute('transactions.create', ['withdrawal']); + } + + public function testCreate() + { + // go! + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $this->be($bill->user); + + // CURRENCY: + $currency = FactoryMuffin::create('FireflyIII\Models\TransactionCurrency'); + Amount::shouldReceive('getDefaultCurrency')->once()->andReturn($currency); + Amount::shouldReceive('getAllCurrencies')->once()->andReturn([$currency]); + + $this->call('GET', '/bills/create'); + $this->assertViewHas('subTitle', 'Create new'); + $this->assertResponseOk(); + + } + + public function testDelete() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $this->be($bill->user); + $this->call('GET', '/bills/delete/' . $bill->id); + $this->assertViewHas('subTitle', 'Delete "' . e($bill->name) . '"'); + $this->assertResponseOk(); + } + + public function testDestroy() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $this->be($bill->user); + + + $this->call('POST', '/bills/destroy/' . $bill->id, ['_token' => 'replaceMe']); + $this->assertSessionHas('success', 'The bill was deleted.'); + $this->assertResponseStatus(302); + } + + public function testEdit() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $this->be($bill->user); + + // CURRENCY: + $currency = FactoryMuffin::create('FireflyIII\Models\TransactionCurrency'); + Amount::shouldReceive('getDefaultCurrency')->once()->andReturn($currency); + Amount::shouldReceive('getAllCurrencies')->once()->andReturn([$currency]); + + $this->call('GET', '/bills/edit/' . $bill->id); + $this->assertViewHas('subTitle', 'Edit "' . e($bill->name) . '"'); + $this->assertResponseOk(); + } + + public function testIndex() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $this->be($bill->user); + + $collection = new Collection; + $collection->push($bill); + + Amount::shouldReceive('format')->andReturn('XX'); + + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository->shouldReceive('getBills')->once()->andReturn($collection); + $repository->shouldReceive('nextExpectedMatch')->with($bill)->andReturn(new Carbon); + $repository->shouldReceive('lastFoundMatch')->with($bill)->andReturn(new Carbon); + + $this->call('GET', '/bills'); + $this->assertResponseOk(); + + } + + public function testRescan() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal'); + $collection = new Collection; + $this->be($bill->user); + $collection->push($journal); + + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository->shouldReceive('getPossiblyRelatedJournals')->once()->andReturn($collection); + $repository->shouldReceive('scan'); + + $this->call('GET', '/bills/rescan/' . $bill->id); + $this->assertResponseStatus(302); + $this->assertSessionHas('success', 'Rescanned everything.'); + + + } + + public function testRescanInactive() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $bill->active = 0; + $bill->save(); + $this->be($bill->user); + + $this->call('GET', '/bills/rescan/' . $bill->id); + $this->assertResponseStatus(302); + $this->assertSessionHas('warning', 'Inactive bills cannot be scanned.'); + + } + + public function testShow() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal'); + $collection = new Collection; + + $bill->save(); + $this->be($bill->user); + $collection->push($journal); + + + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository->shouldReceive('getJournals')->once()->andReturn($collection); + $repository->shouldReceive('nextExpectedMatch')->once()->andReturn(new Carbon); + + Amount::shouldReceive('format')->andReturn('XX'); + Amount::shouldReceive('getCurrencyCode')->andReturn('XX'); + + $this->call('GET', '/bills/show/' . $bill->id); + } + + public function testStore() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $request = $this->mock('FireflyIII\Http\Requests\BillFormRequest'); + + $this->be($bill->user); + $request->shouldReceive('getBillData')->once()->andReturn([]); + $repository->shouldReceive('store')->with([])->andReturn($bill); + + $this->call('POST', '/bills/store', ['_token' => 'replaceMe']); + $this->assertResponseStatus(302); + $this->assertSessionHas('success', 'Bill "' . e($bill->name) . '" stored.'); + } + + public function testStoreAndRedirect() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $request = $this->mock('FireflyIII\Http\Requests\BillFormRequest'); + + $this->be($bill->user); + $request->shouldReceive('getBillData')->once()->andReturn([]); + $repository->shouldReceive('store')->with([])->andReturn($bill); + + $this->call('POST', '/bills/store', ['_token' => 'replaceMe', 'create_another' => 1]); + $this->assertResponseStatus(302); + $this->assertSessionHas('success', 'Bill "' . e($bill->name) . '" stored.'); + } + + public function testUpdate() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $request = $this->mock('FireflyIII\Http\Requests\BillFormRequest'); + + $this->be($bill->user); + $request->shouldReceive('getBillData')->once()->andReturn([]); + $repository->shouldReceive('update')->andReturn($bill); + + $this->call('POST', '/bills/update/' . $bill->id, ['_token' => 'replaceMe']); + $this->assertResponseStatus(302); + $this->assertSessionHas('success', 'Bill "' . e($bill->name) . '" updated.'); + } + + public function testUpdateAndRedirect() + { + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $request = $this->mock('FireflyIII\Http\Requests\BillFormRequest'); + + $this->be($bill->user); + $request->shouldReceive('getBillData')->once()->andReturn([]); + $repository->shouldReceive('update')->andReturn($bill); + + $this->call('POST', '/bills/update/' . $bill->id, ['_token' => 'replaceMe', 'return_to_edit' => 1]); + $this->assertResponseStatus(302); + + } +} \ No newline at end of file diff --git a/tests/factories/all.php b/tests/factories/all.php index 74b3792ce3..4caa0bd864 100644 --- a/tests/factories/all.php +++ b/tests/factories/all.php @@ -27,109 +27,147 @@ if (!class_exists('RandomString')) { } } + FactoryMuffin::define( - 'FireflyIII\Models\Account', [ - 'user_id' => 'factory|FireflyIII\User', - 'account_type_id' => 'factory|FireflyIII\Models\AccountType', - 'name' => 'word', - 'active' => 'boolean', - 'encrypted' => 'boolean', - 'virtual_balance' => 0 - ] + 'FireflyIII\Models\Bill', + [ + 'user_id' => 'factory|FireflyIII\User', + 'name' => 'sentence', + 'match' => function () { + $words = []; + for ($i = 0; $i < 3; $i++) { + $words[] = RandomString::generateRandomString(5); + } + + return join(',', $words); + }, + 'amount_min' => 10, + 'amount_max' => 20, + 'date' => 'date', + 'active' => 1, + 'automatch' => 1, + 'repeat_freq' => 'monthly', + 'skip' => 0, + 'name_encrypted' => 1, + 'match_encrypted' => 1, + + ] ); FactoryMuffin::define( - 'FireflyIII\Models\Budget', [ - 'user_id' => 'factory|FireflyIII\User', - 'name' => 'sentence', - 'active' => 'boolean', - 'encrypted' => 1, - ] + 'FireflyIII\Models\Account', + [ + 'user_id' => 'factory|FireflyIII\User', + 'account_type_id' => 'factory|FireflyIII\Models\AccountType', + 'name' => 'word', + 'active' => 'boolean', + 'encrypted' => 'boolean', + 'virtual_balance' => 0 + ] ); FactoryMuffin::define( - 'FireflyIII\Models\LimitRepetition', [ - 'budget_limit_id' => 'factory|FireflyIII\Models\BudgetLimit', - 'startdate' => 'date', - 'enddate' => 'date', - 'amount' => 'integer', - ] + 'FireflyIII\Models\Budget', + [ + 'user_id' => 'factory|FireflyIII\User', + 'name' => 'sentence', + 'active' => 'boolean', + 'encrypted' => 1, + ] ); FactoryMuffin::define( - 'FireflyIII\Models\BudgetLimit', [ - 'budget_id' => 'factory|FireflyIII\Models\Budget', - 'startdate' => 'date', - 'amount' => 'integer', - 'repeats' => 'false', - 'repeat_freq' => 'monthly', + 'FireflyIII\Models\LimitRepetition', + [ + 'budget_limit_id' => 'factory|FireflyIII\Models\BudgetLimit', + 'startdate' => 'date', + 'enddate' => 'date', + 'amount' => 'integer', + ] +); - ] +FactoryMuffin::define( + 'FireflyIII\Models\BudgetLimit', + [ + 'budget_id' => 'factory|FireflyIII\Models\Budget', + 'startdate' => 'date', + 'amount' => 'integer', + 'repeats' => 'false', + 'repeat_freq' => 'monthly', + + ] ); FactoryMuffin::define( - 'FireflyIII\Models\Preference', [ - 'name' => 'word', - 'data' => 'sentence', - 'user_id' => 'factory|FireflyIII\User', - ] + 'FireflyIII\Models\Preference', + [ + 'name' => 'word', + 'data' => 'sentence', + 'user_id' => 'factory|FireflyIII\User', + ] ); FactoryMuffin::define( - 'FireflyIII\Models\AccountType', [ - 'type' => function () { - $types = ['Expense account', 'Revenue account', 'Asset account']; - $count = DB::table('account_types')->count(); + 'FireflyIII\Models\AccountType', + [ + 'type' => function () { + $types = ['Expense account', 'Revenue account', 'Asset account']; + $count = DB::table('account_types')->count(); - return $types[$count]; - }, - 'editable' => 1, - ] + return $types[$count]; + }, + 'editable' => 1, + ] ); FactoryMuffin::define( - 'FireflyIII\Models\TransactionCurrency', [ - 'code' => function () { - return RandomString::generateRandomString(3); - }, - 'symbol' => function () { - return RandomString::generateRandomString(1); - }, - 'name' => 'word' - ] + 'FireflyIII\Models\TransactionCurrency', + [ + 'code' => function () { + return RandomString::generateRandomString(3); + }, + 'symbol' => function () { + return RandomString::generateRandomString(1); + }, + 'name' => 'word' + ] ); FactoryMuffin::define( - 'FireflyIII\User', [ - 'email' => 'email', - 'password' => bcrypt('james'), - ] + 'FireflyIII\User', + [ + 'email' => 'email', + 'password' => bcrypt('james'), + ] ); FactoryMuffin::define( - 'FireflyIII\Models\Transaction', [ - 'transaction_journal_id' => 'factory|FireflyIII\Models\TransactionJournal', - 'amount' => 'integer', - 'account_id' => 'factory|FireflyIII\Models\Account' - ] + 'FireflyIII\Models\Transaction', + [ + 'transaction_journal_id' => 'factory|FireflyIII\Models\TransactionJournal', + 'amount' => 'integer', + 'account_id' => 'factory|FireflyIII\Models\Account' + ] ); FactoryMuffin::define( - 'FireflyIII\Models\TransactionType', [ - 'type' => 'word', - ] + 'FireflyIII\Models\TransactionType', + [ + 'type' => 'word', + ] ); FactoryMuffin::define( - 'FireflyIII\Models\TransactionJournal', [ - 'user_id' => 'factory|FireflyIII\User', - 'transaction_type_id' => 'factory|FireflyIII\Models\TransactionType', - 'transaction_currency_id' => 'factory|FireflyIII\Models\TransactionCurrency', - 'description' => 'sentence', - 'completed' => '1', - 'date' => 'date', - 'encrypted' => '1', - 'order' => '0', - ] + 'FireflyIII\Models\TransactionJournal', + [ + 'user_id' => 'factory|FireflyIII\User', + 'transaction_type_id' => 'factory|FireflyIII\Models\TransactionType', + 'transaction_currency_id' => 'factory|FireflyIII\Models\TransactionCurrency', + 'description' => 'sentence', + 'completed' => '1', + 'date' => 'date', + 'encrypted' => '1', + 'order' => '0', + ] ); \ No newline at end of file