Lots of new tests.

This commit is contained in:
James Cole 2015-04-07 10:14:10 +02:00
parent b2674971f1
commit 701efb943d
9 changed files with 355 additions and 86 deletions

View File

@ -1,8 +1,6 @@
<?php namespace FireflyIII\Http\Controllers;
use Amount;
use App;
use Auth;
use Carbon\Carbon;
use Crypt;
use DB;
@ -245,18 +243,16 @@ class GoogleChartController extends Controller
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function billsOverview(GChart $chart, BillRepositoryInterface $repository)
public function billsOverview(GChart $chart, BillRepositoryInterface $repository, AccountRepositoryInterface $accounts)
{
$chart->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();

View File

@ -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
*/

View File

@ -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
*

View File

@ -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
*

View File

@ -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;
}
}

View File

@ -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
*

View File

@ -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'));
}
}

View File

@ -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

View File

@ -1,5 +1,10 @@
<?php
use Carbon\Carbon;
use FireflyIII\Models\Preference;
use Illuminate\Support\Collection;
use League\FactoryMuffin\Facade as FactoryMuffin;
/**
* Class GoogleChartControllerTest
*/
@ -36,42 +41,167 @@ class GoogleChartControllerTest extends TestCase
public function testAccountBalanceChart()
{
$this->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()