From 701efb943df2f9782ee2b5adacc7905312d31e44 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 7 Apr 2015 10:14:10 +0200 Subject: [PATCH] Lots of new tests. --- .../Controllers/GoogleChartController.php | 100 +++++------- app/Models/TransactionJournal.php | 13 ++ .../Account/AccountRepository.php | 52 ++++++- .../Account/AccountRepositoryInterface.php | 16 ++ app/Repositories/Bill/BillRepository.php | 71 +++++++-- .../Bill/BillRepositoryInterface.php | 24 +++ app/Repositories/Budget/BudgetRepository.php | 11 ++ .../Budget/BudgetRepositoryInterface.php | 8 + .../controllers/GoogleChartControllerTest.php | 146 +++++++++++++++++- 9 files changed, 355 insertions(+), 86 deletions(-) diff --git a/app/Http/Controllers/GoogleChartController.php b/app/Http/Controllers/GoogleChartController.php index 9285487ab0..e3117a6616 100644 --- a/app/Http/Controllers/GoogleChartController.php +++ b/app/Http/Controllers/GoogleChartController.php @@ -1,8 +1,6 @@ addColumn('Name', 'string'); $chart->addColumn('Amount', 'number'); - - $paid = ['items' => [], 'amount' => 0]; - $unpaid = ['items' => [], 'amount' => 0]; $start = Session::get('start', Carbon::now()->startOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth()); - - $bills = $repository->getActiveBills(); + $bills = $repository->getActiveBills(); + $paid = new Collection; // journals. + $unpaid = new Collection; // bills /** @var Bill $bill */ foreach ($bills as $bill) { @@ -264,71 +260,55 @@ class GoogleChartController extends Controller foreach ($ranges as $range) { // paid a bill in this range? - $count = $bill->transactionjournals()->before($range['end'])->after($range['start'])->count(); - if ($count == 0) { - $unpaid['items'][] = $bill->name . ' (' . $range['start']->format('jS M Y') . ')'; - $unpaid['amount'] += ($bill->amount_max + $bill->amount_min / 2); - + $journals = $repository->getJournalsInRange($bill, $range['start'], $range['end']); + if ($journals->count() == 0) { + $unpaid->push([$bill, $range['start']]); } else { - $journal = $bill->transactionjournals()->with('transactions')->before($range['end'])->after($range['start'])->first(); - $paid['items'][] = $journal->description; - $amount = 0; - foreach ($journal->transactions as $t) { - if (floatval($t->amount) > 0) { - $amount = floatval($t->amount); - } - } - $paid['amount'] += $amount; + $paid = $paid->merge($journals); } } } - /** - * Find credit card accounts and possibly unpaid credit card bills. - */ - $creditCards = Auth::user()->accounts() - ->hasMetaValue('accountRole', 'ccAsset') - ->hasMetaValue('ccType', 'monthlyFull') - ->get( - [ - 'accounts.*', - 'ccType.data as ccType', - 'accountRole.data as accountRole' - ] - ); - // if the balance is not zero, the monthly payment is still underway. - /** @var Account $creditCard */ + $creditCards = $accounts->getCreditCards(); foreach ($creditCards as $creditCard) { $balance = Steam::balance($creditCard, null, true); $date = new Carbon($creditCard->getMeta('ccMonthlyPaymentDate')); if ($balance < 0) { - // unpaid! - $unpaid['amount'] += $balance * -1; - $unpaid['items'][] = $creditCard->name . ' (expected ' . Amount::format(($balance * -1), false) . ') on the ' . $date->format('jS') . ')'; + // unpaid! create a fake bill that matches the amount. + $description = $creditCard->name; + $amount = $balance * -1; + $fakeBill = $repository->createFakeBill($description, $date, $amount); + $unpaid->push([$fakeBill, $date]); } if ($balance == 0) { - // find a transfer TO the credit card which should account for + // find transfer(s) TO the credit card which should account for // anything paid. If not, the CC is not yet used. - $transactions = $creditCard->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->before($end)->after($start)->get(); - if ($transactions->count() > 0) { - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $journal = $transaction->transactionJournal; - if ($journal->transactionType->type == 'Transfer') { - $paid['amount'] += floatval($transaction->amount); - $paid['items'][] = $creditCard->name . - ' (paid ' . Amount::format((floatval($transaction->amount)), false) . - ' on the ' . $journal->date->format('jS') . ')'; - } - } - } + $journals = $accounts->getTransfersInRange($creditCard, $start, $end); + $paid = $paid->merge($journals); } } - $chart->addRow('Unpaid: ' . join(', ', $unpaid['items']), $unpaid['amount']); - $chart->addRow('Paid: ' . join(', ', $paid['items']), $paid['amount']); + // loop paid and create single entry: + $paidDescriptions = []; + $paidAmount = 0; + $unpaidDescriptions = []; + $unpaidAmount = 0; + + /** @var TransactionJournal $entry */ + foreach ($paid as $entry) { + $paidDescriptions[] = $entry->description; + $paidAmount += floatval($entry->amount); + } + + // loop unpaid: + /** @var Bill $entry */ + foreach ($unpaid as $entry) { + $unpaidDescriptions[] = $entry[0]->name . ' (' . $entry[1]->format('jS M Y') . ')'; + $unpaidAmount += ($bill->amount_max + $bill->amount_min / 2); + } + + $chart->addRow('Unpaid: ' . join(', ', $unpaidDescriptions), $unpaidAmount); + $chart->addRow('Paid: ' . join(', ', $paidDescriptions), $paidAmount); $chart->generate(); return Response::json($chart->getData()); @@ -341,7 +321,7 @@ class GoogleChartController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function budgetLimitSpending(Budget $budget, LimitRepetition $repetition, GChart $chart) + public function budgetLimitSpending(Budget $budget, LimitRepetition $repetition, GChart $chart, BudgetRepositoryInterface $repository) { $start = clone $repetition->startdate; $end = $repetition->enddate; @@ -356,7 +336,7 @@ class GoogleChartController extends Controller /* * Sum of expenses on this day: */ - $sum = floatval($budget->transactionjournals()->lessThan(0)->transactionTypes(['Withdrawal'])->onDate($start)->sum('amount')); + $sum = $repository->expensesOnDay($budget, $start); $amount += $sum; $chart->addRow(clone $start, $amount); $start->addDay(); diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 2745f8379f..d9af19b51e 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -55,6 +55,19 @@ class TransactionJournal extends Model return $this->belongsToMany('FireflyIII\Models\Category'); } + /** + * @return float + */ + public function getAmountAttribute() + { + /** @var Transaction $t */ + foreach ($this->transactions as $t) { + if ($t->amount > 0) { + return floatval($t->amount); + } + } + } + /** * @return array */ diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index b09f53bdae..50807a32b9 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -66,13 +66,33 @@ class AccountRepository implements AccountRepositoryInterface )->accountTypeIn($types)->orderBy('accounts.name', 'ASC'); if ($page == -1) { - return $query->get(['accounts.*']); + $result = $query->get(['accounts.*']); } else { $size = 50; $offset = ($page - 1) * $size; - return $query->take($size)->offset($offset)->get(['accounts.*']); + $result = $query->take($size)->offset($offset)->get(['accounts.*']); } + + return $result; + } + + + /** + * @return Collection + */ + public function getCreditCards() + { + return Auth::user()->accounts() + ->hasMetaValue('accountRole', 'ccAsset') + ->hasMetaValue('ccType', 'monthlyFull') + ->get( + [ + 'accounts.*', + 'ccType.data as ccType', + 'accountRole.data as accountRole' + ] + ); } /** @@ -219,6 +239,34 @@ class AccountRepository implements AccountRepositoryInterface return $accounts; } + /** + * Get all transfers TO this account in this range. + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getTransfersInRange(Account $account, Carbon $start, Carbon $end) + { + return TransactionJournal::whereIn( + 'id', function ($q) use ($account, $start, $end) { + $q->select('transaction_journals.id') + ->from('transactions') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->where('transactions.account_id', $account->id) + ->where('transaction_journals.user_id', Auth::user()->id) + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('transactions.amount', '>', 0) + ->where('transaction_types.type', 'Transfer'); + + } + )->get(); + } + /** * @param Account $account * diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index d064db673a..280cf41a21 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -46,6 +46,22 @@ interface AccountRepositoryInterface */ public function getFirstTransaction(TransactionJournal $journal, Account $account); + /** + * @return Collection + */ + public function getCreditCards(); + + /** + * Get all transfers TO this account in this range. + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getTransfersInRange(Account $account, Carbon $start, Carbon $end); + /** * @param Preference $preference * diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 837713d87b..9b7ae4e492 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -19,6 +19,31 @@ use Navigation; */ class BillRepository implements BillRepositoryInterface { + /** + * Create a fake bill to help the chart controller. + * + * @param string $description + * @param Carbon $date + * @param float $amount + * + * @return Bill + */ + public function createFakeBill($description, Carbon $date, $amount) + { + $bill = new Bill; + $bill->name = $description; + $bill->match = $description; + $bill->amount_min = $amount; + $bill->amount_max = $amount; + $bill->date = $date; + $bill->repeat_freq = 'monthly'; + $bill->skip = 0; + $bill->automatch = false; + $bill->active = false; + + return $bill; + } + /** * @param Bill $bill * @@ -29,6 +54,22 @@ class BillRepository implements BillRepositoryInterface return $bill->delete(); } + /** + * @return Collection + */ + public function getActiveBills() + { + /** @var Collection $set */ + $set = Auth::user()->bills()->orderBy('name', 'ASC')->where('active', 1)->get(); + $set->sort( + function (Bill $bill) { + return $bill->name; + } + ); + + return $set; + } + /** * @return Collection */ @@ -65,6 +106,20 @@ class BillRepository implements BillRepositoryInterface ->get(['transaction_journals.*', 'transactions.amount']); } + /** + * Get all journals that were recorded on this bill between these dates. + * + * @param Bill $bill + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getJournalsInRange(Bill $bill, Carbon $start, Carbon $end) + { + return $bill->transactionjournals()->before($end)->after($start)->get(); + } + /** * @param Bill $bill * @@ -322,20 +377,4 @@ class BillRepository implements BillRepositoryInterface return $bill; } - - /** - * @return Collection - */ - public function getActiveBills() - { - /** @var Collection $set */ - $set = Auth::user()->bills()->orderBy('name', 'ASC')->where('active', 1)->get(); - $set->sort( - function (Bill $bill) { - return $bill->name; - } - ); - - return $set; - } } diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index a0fbe7eac9..5e6041710b 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -15,6 +15,17 @@ use Illuminate\Support\Collection; interface BillRepositoryInterface { + /** + * Create a fake bill to help the chart controller. + * + * @param string $description + * @param Carbon $date + * @param float $amount + * + * @return Bill + */ + public function createFakeBill($description, Carbon $date, $amount); + /** * @param Bill $bill * @@ -39,6 +50,17 @@ interface BillRepositoryInterface */ public function getJournals(Bill $bill); + /** + * Get all journals that were recorded on this bill between these dates. + * + * @param Bill $bill + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getJournalsInRange(Bill $bill, Carbon $start, Carbon $end); + /** * @param Bill $bill * @@ -66,6 +88,8 @@ interface BillRepositoryInterface */ public function lastFoundMatch(Bill $bill); + + /** * @param Bill $bill * diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 1fa943c0cd..21838900e4 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -308,4 +308,15 @@ class BudgetRepository implements BudgetRepositoryInterface } + + /** + * @param Budget $budget + * @param Carbon $date + * + * @return float + */ + public function expensesOnDay(Budget $budget, Carbon $date) + { + return floatval($budget->transactionjournals()->lessThan(0)->transactionTypes(['Withdrawal'])->onDate($date)->sum('amount')); + } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index c1d8e1c230..eb61d7370d 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -107,6 +107,14 @@ interface BudgetRepositoryInterface */ public function store(array $data); + /** + * @param Budget $budget + * @param Carbon $date + * + * @return float + */ + public function expensesOnDay(Budget $budget, Carbon $date); + /** * @param Budget $budget * @param Carbon $start diff --git a/tests/controllers/GoogleChartControllerTest.php b/tests/controllers/GoogleChartControllerTest.php index 03e1c5fe9f..5637734821 100644 --- a/tests/controllers/GoogleChartControllerTest.php +++ b/tests/controllers/GoogleChartControllerTest.php @@ -1,5 +1,10 @@ markTestIncomplete(); + $account = FactoryMuffin::create('FireflyIII\Models\Account'); + $this->be($account->user); + + // mock stuff: + Steam::shouldReceive('balance')->andReturn(0); + + $this->call('GET', '/chart/account/' . $account->id); + $this->assertResponseOk(); } public function testAllAccountsBalanceChart() { - $this->markTestIncomplete(); + $account = FactoryMuffin::create('FireflyIII\Models\Account'); + $this->be($account->user); + $collection = new Collection; + $collection->push($account); + + //mock stuff: + Preferences::shouldReceive('get')->andReturn(new Preference); + $repository = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + $repository->shouldReceive('getFrontpageAccounts')->andReturn($collection); + + $this->call('GET', '/chart/home/account'); + $this->assertResponseOk(); + + } public function testAllBudgetsAndSpending() { - $this->markTestIncomplete(); + $budget = FactoryMuffin::create('FireflyIII\Models\Budget'); + $this->be($budget->user); + $collection = new Collection; + $collection->push($budget); + + // mock stuff: + $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository->shouldReceive('getBudgets')->andReturn($collection); + $repository->shouldReceive('spentInMonth')->andReturn(rand(1, 100)); + + $this->call('GET', '/chart/budgets/spending/2015'); + $this->assertResponseOk(); } public function testAllBudgetsHomeChart() { - $this->markTestIncomplete(); + $budget = FactoryMuffin::create('FireflyIII\Models\Budget'); + $budget1 = FactoryMuffin::create('FireflyIII\Models\Budget'); + $this->be($budget->user); + + $start = Carbon::now()->startOfMonth(); + $end = Carbon::now()->endOfMonth(); + + $repetition = FactoryMuffin::create('FireflyIII\Models\LimitRepetition'); + $repetitions = new Collection; + $repetitions->push($repetition); + $emptyRepetitions = new Collection; + + $collection = new Collection; + $collection->push($budget); + $collection->push($budget1); + + // mock stuff: + $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository->shouldReceive('getBudgets')->andReturn($collection); + $repository->shouldReceive('getBudgetLimitRepetitions')->once()->andReturn($repetitions, $emptyRepetitions); + $repository->shouldReceive('sumBudgetExpensesInPeriod')->andReturn(12); + $repository->shouldReceive('getWithoutBudgetSum')->andReturn(0); + + $this->call('GET', '/chart/home/budgets'); + $this->assertResponseOk(); } public function testAllCategoriesHomeChart() { - $this->markTestIncomplete(); + $category = FactoryMuffin::create('FireflyIII\Models\Category'); + + $this->be($category->user); + $category->save(); + $category->sum = 100; + $collection = new Collection; + $collection->push($category); + + // mock stuff: + $repository = $this->mock('FireflyIII\Repositories\Category\CategoryRepositoryInterface'); + $repository->shouldReceive('getCategoriesAndExpenses')->andReturn($collection); + Crypt::shouldReceive('decrypt')->andReturn('Hello!'); + Crypt::shouldReceive('encrypt')->andReturn('Hello!'); + + + $this->call('GET', '/chart/home/categories'); + $this->assertResponseOk(); } public function testBillOverview() { - $this->markTestIncomplete(); + $bill = FactoryMuffin::create('FireflyIII\Models\Bill'); + $journal = FactoryMuffin::create('FireflyIII\Models\TransactionJournal'); + $collection = new Collection; + $collection->push($journal); + $this->be($bill->user); + + // mock! + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $repository->shouldReceive('getJournals')->andReturn($collection); + + + // call! + $this->call('GET', '/chart/bills/' . $bill->id); + $this->assertResponseOk(); } public function testBillsOverview() { - $this->markTestIncomplete(); + $bill1 = FactoryMuffin::create('FireflyIII\Models\Bill'); + $bill2 = FactoryMuffin::create('FireflyIII\Models\Bill'); + $journal1 = FactoryMuffin::create('FireflyIII\Models\TransactionJournal'); + $journal2 = FactoryMuffin::create('FireflyIII\Models\TransactionJournal'); + $card1 = FactoryMuffin::create('FireflyIII\Models\Account'); + $card2 = FactoryMuffin::create('FireflyIII\Models\Account'); + $fake = FactoryMuffin::create('FireflyIII\Models\Bill'); + + + $bills = new Collection([$bill1, $bill2]); + $journals = new Collection([$journal1, $journal2]); + $cards = new Collection([$card1, $card2]); + $emptyCollection = new Collection; + $ranges = [['start' => new Carbon, 'end' => new Carbon]]; + $this->be($bill1->user); + + // mock! + $repository = $this->mock('FireflyIII\Repositories\Bill\BillRepositoryInterface'); + $accounts = $this->mock('FireflyIII\Repositories\Account\AccountRepositoryInterface'); + + // calls: + $repository->shouldReceive('getActiveBills')->andReturn($bills); + $repository->shouldReceive('getRanges')->andReturn($ranges); + $repository->shouldReceive('getJournalsInRange')->andReturn($journals, $emptyCollection); + $accounts->shouldReceive('getCreditCards')->andReturn($cards); + $accounts->shouldReceive('getTransfersInRange')->andReturn($journals, $emptyCollection); + $repository->shouldReceive('createFakeBill')->andReturn($fake); + Steam::shouldReceive('balance')->andReturn(-1, 0); + + $this->call('GET', '/chart/home/bills'); + $this->assertResponseOk(); } public function testBudgetLimitSpending() { - $this->markTestIncomplete(); + $repetition = FactoryMuffin::create('FireflyIII\Models\LimitRepetition'); + $budget = $repetition->budgetlimit->budget; + $this->be($budget->user); + ///chart/budget/{budget}/{limitrepetition} + + // mock! + $repository = $this->mock('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); + $repository->shouldReceive('expensesOnDay')->andReturn(rand(1, 1000)); + + $this->call('GET', '/chart/budget/' . $budget->id . '/' . $repetition->id); + $this->assertResponseOk(); + } public function testBudgetsAndSpending()