diff --git a/app/Support/Models/TagTrait.php b/app/Support/Models/TagTrait.php index 83d59ad292..c99a276f92 100644 --- a/app/Support/Models/TagTrait.php +++ b/app/Support/Models/TagTrait.php @@ -11,10 +11,13 @@ declare(strict_types = 1); namespace FireflyIII\Support\Models; -use FireflyIII\Models\Tag; -use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; /** + * + * @property Collection $transactionjournals + * @property string $tagMode + * * Class TagSupport * * @package FireflyIII\Support\Models diff --git a/app/Support/Models/TransactionJournalTrait.php b/app/Support/Models/TransactionJournalTrait.php index 14a6dfbb3d..7ae16ce215 100644 --- a/app/Support/Models/TransactionJournalTrait.php +++ b/app/Support/Models/TransactionJournalTrait.php @@ -24,11 +24,14 @@ use Illuminate\Support\Collection; /** * Class TransactionJournalTrait * + * @property int $id + * @method Collection transactions() + * @method bool isWithdrawal() + * * @package FireflyIII\Support\Models */ trait TransactionJournalTrait { - /** * @return string * @throws FireflyException @@ -40,7 +43,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('amount'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } // saves on queries: @@ -65,7 +68,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('amount-positive'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } // saves on queries: @@ -147,7 +150,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('destination-account-list'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $transactions = $this->transactions()->where('amount', '>', 0)->orderBy('transactions.account_id')->with('account')->get(); $list = new Collection; @@ -170,7 +173,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('destination-transaction-list'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $list = $this->transactions()->where('amount', '>', 0)->with('account')->get(); $cache->store($list); @@ -221,7 +224,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('source-account-list'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $transactions = $this->transactions()->where('amount', '<', 0)->orderBy('transactions.account_id')->with('account')->get(); $list = new Collection; @@ -244,7 +247,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('source-transaction-list'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $list = $this->transactions()->where('amount', '<', 0)->with('account')->get(); $cache->store($list); @@ -262,7 +265,7 @@ trait TransactionJournalTrait $cache->addProperty('transaction-journal'); $cache->addProperty('type-string'); if ($cache->has()) { - return $cache->get(); + return $cache->get(); // @codeCoverageIgnore } $typeStr = $this->transaction_type_type ?? $this->transactionType->type; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 8a65c4ee73..92cc9d698f 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -32,3 +32,13 @@ $factory->define( ]; } ); + +$factory->define( + FireflyIII\Models\Transaction::class, function (Faker\Generator $faker) { + return [ + 'transaction_amount' => strval($faker->randomFloat(2, -100, 100)), + 'opposing_account_id' => $faker->numberBetween(1, 10), + 'opposing_account_name' => $faker->words(3), + ]; +} +); \ No newline at end of file diff --git a/tests/Feature/Controllers/Report/OperationsControllerTest.php b/tests/Feature/Controllers/Report/OperationsControllerTest.php index 66380b23bf..b8b01c3417 100644 --- a/tests/Feature/Controllers/Report/OperationsControllerTest.php +++ b/tests/Feature/Controllers/Report/OperationsControllerTest.php @@ -12,27 +12,52 @@ declare(strict_types = 1); namespace Tests\Feature\Controllers\Report; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionType; use Tests\TestCase; class OperationsControllerTest extends TestCase { /** * @covers \FireflyIII\Http\Controllers\Report\OperationsController::expenses + * @covers \FireflyIII\Http\Controllers\Report\OperationsController::getExpenseReport + * @covers \FireflyIII\Http\Controllers\Report\OperationsController::groupByOpposing */ public function testExpenses() { + $transactions = factory(Transaction::class, 10)->make(); + $collector = $this->mock(JournalCollectorInterface::class); + $collector->shouldReceive('setAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('setTypes')->withArgs([[TransactionType::WITHDRAWAL, TransactionType::TRANSFER]])->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('enableInternalFilter')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn($transactions); + + $this->be($this->user()); - $response = $this->get(route('report-data.operations.expenses', ['1', '20120101', '20120131'])); + $response = $this->get(route('report-data.operations.expenses', ['1', '20160101', '20160131'])); $response->assertStatus(200); } /** * @covers \FireflyIII\Http\Controllers\Report\OperationsController::income + * @covers \FireflyIII\Http\Controllers\Report\OperationsController::getIncomeReport */ public function testIncome() { + $transactions = factory(Transaction::class, 10)->make(); + $collector = $this->mock(JournalCollectorInterface::class); + $collector->shouldReceive('setAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('setTypes')->withArgs([[TransactionType::DEPOSIT, TransactionType::TRANSFER]])->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('enableInternalFilter')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn($transactions); + $this->be($this->user()); - $response = $this->get(route('report-data.operations.income', ['1', '20120101', '20120131'])); + $response = $this->get(route('report-data.operations.income', ['1', '20160101', '20160131'])); $response->assertStatus(200); } @@ -42,7 +67,7 @@ class OperationsControllerTest extends TestCase public function testOperations() { $this->be($this->user()); - $response = $this->get(route('report-data.operations.operations', ['1', '20120101', '20120131'])); + $response = $this->get(route('report-data.operations.operations', ['1', '20160101', '20160131'])); $response->assertStatus(200); } diff --git a/tests/Feature/Controllers/Transaction/ConvertControllerTest.php b/tests/Feature/Controllers/Transaction/ConvertControllerTest.php index dbb44283f0..d0e5e2ab4c 100644 --- a/tests/Feature/Controllers/Transaction/ConvertControllerTest.php +++ b/tests/Feature/Controllers/Transaction/ConvertControllerTest.php @@ -12,10 +12,13 @@ declare(strict_types = 1); namespace Tests\Feature\Controllers\Transaction; +use DB; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Support\Collection; +use Illuminate\Support\MessageBag; use Tests\TestCase; /** @@ -47,6 +50,10 @@ class ConvertControllerTest extends TestCase */ public function testIndexDepositWithdrawal() { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + $deposit = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)->first(); $this->be($this->user()); $response = $this->get(route('transactions.convert.index', ['withdrawal', $deposit->id])); @@ -54,11 +61,52 @@ class ConvertControllerTest extends TestCase $response->assertSee('Convert a deposit into a withdrawal'); } + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::index + */ + public function testIndexSameType() + { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + + $this->be($this->user()); + $deposit = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)->first(); + $response = $this->get(route('transactions.convert.index', ['deposit', $deposit->id])); + $response->assertStatus(302); + $response->assertSessionHas('info'); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::index + */ + public function testIndexSplit() + { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + + $this->be($this->user()); + $withdrawal = TransactionJournal::where('transaction_type_id', 1) + ->whereNull('transaction_journals.deleted_at') + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->groupBy('transaction_journals.id') + ->orderBy('ct', 'DESC') + ->where('user_id', $this->user()->id)->first(['transaction_journals.id', DB::raw('count(transactions.`id`) as ct')]); + $response = $this->get(route('transactions.convert.index', ['deposit', $withdrawal->id])); + $response->assertStatus(302); + $response->assertSessionHas('error'); + } + /** * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::index */ public function testIndexTransferDeposit() { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + $transfer = TransactionJournal::where('transaction_type_id', 3)->where('user_id', $this->user()->id)->first(); $this->be($this->user()); $response = $this->get(route('transactions.convert.index', ['deposit', $transfer->id])); @@ -71,6 +119,10 @@ class ConvertControllerTest extends TestCase */ public function testIndexTransferWithdrawal() { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + $transfer = TransactionJournal::where('transaction_type_id', 3)->where('user_id', $this->user()->id)->first(); $this->be($this->user()); $response = $this->get(route('transactions.convert.index', ['withdrawal', $transfer->id])); @@ -83,6 +135,10 @@ class ConvertControllerTest extends TestCase */ public function testIndexWithdrawalDeposit() { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + $withdrawal = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->first(); $this->be($this->user()); $response = $this->get(route('transactions.convert.index', ['deposit', $withdrawal->id])); @@ -95,6 +151,10 @@ class ConvertControllerTest extends TestCase */ public function testIndexWithdrawalTransfer() { + // mock stuff: + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getActiveAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->once()->andReturn(new Collection); + $withdrawal = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->first(); $this->be($this->user()); $response = $this->get(route('transactions.convert.index', ['transfer', $withdrawal->id])); @@ -107,11 +167,169 @@ class ConvertControllerTest extends TestCase * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount */ - public function testPostIndex() + public function testPostIndexDepositTransfer() { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $deposit = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)->first(); + $data = ['source_account_asset' => 1]; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['transfer', $deposit->id]), $data); + $response->assertStatus(302); + $response->assertRedirect(route('transactions.show', [$deposit->id])); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexDepositWithdrawal() + { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $deposit = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)->first(); + $data = ['destination_account_expense' => 'New expense name.',]; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['withdrawal', $deposit->id]), $data); + $response->assertStatus(302); + $response->assertRedirect(route('transactions.show', [$deposit->id])); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexErrored() + { + // mock stuff + $messageBag = new MessageBag; + $messageBag->add('fake', 'fake error'); + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn($messageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $withdrawal = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->first(); - // convert a withdrawal to a transfer. Requires the ID of another asset account. - $data = [ + $data = [ + 'destination_account_asset' => 2, + ]; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['transfer', $withdrawal->id]), $data); + $response->assertStatus(302); + $response->assertRedirect(route('transactions.convert.index', ['transfer', $withdrawal->id])); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexSameType() + { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $withdrawal = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->first(); + $data = [ + 'destination_account_asset' => 2, + ]; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['withdrawal', $withdrawal->id]), $data); + $response->assertStatus(302); + $response->assertSessionHas('error'); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexSplit() + { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $withdrawal = TransactionJournal::where('transaction_type_id', 1) + ->whereNull('transaction_journals.deleted_at') + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->groupBy('transaction_journals.id') + ->orderBy('ct', 'DESC') + ->where('user_id', $this->user()->id)->first(['transaction_journals.id', DB::raw('count(transactions.`id`) as ct')]); + $data = [ + 'destination_account_asset' => 2, + ]; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['transfer', $withdrawal->id]), $data); + $response->assertStatus(302); + $response->assertSessionHas('error'); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexTransferDeposit() + { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $transfer = TransactionJournal::where('transaction_type_id', 3)->where('user_id', $this->user()->id)->first(); + $data = ['source_account_revenue' => 'New rev']; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['deposit', $transfer->id]), $data); + $response->assertStatus(302); + $response->assertRedirect(route('transactions.show', [$transfer->id])); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexWithdrawalDeposit() + { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $withdrawal = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->first(); + $data = ['source_account_revenue' => 'New revenue name.',]; + $this->be($this->user()); + $response = $this->post(route('transactions.convert.index', ['deposit', $withdrawal->id]), $data); + $response->assertStatus(302); + $response->assertRedirect(route('transactions.show', [$withdrawal->id])); + } + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::postIndex + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getSourceAccount + * @covers \FireflyIII\Http\Controllers\Transaction\ConvertController::getDestinationAccount + */ + public function testPostIndexWithdrawalTransfer() + { + // mock stuff + $repository = $this->mock(JournalRepositoryInterface::class); + $repository->shouldReceive('convert')->andReturn(new MessageBag); + $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); + + $withdrawal = TransactionJournal::where('transaction_type_id', 1)->where('user_id', $this->user()->id)->first(); + $data = [ 'destination_account_asset' => 2, ]; $this->be($this->user());