More stuff.

This commit is contained in:
James Cole 2015-05-17 12:49:09 +02:00
parent 033f5b67db
commit b123860304
15 changed files with 301 additions and 81 deletions

View File

@ -21,6 +21,9 @@ class BalanceEntry
/** @var float */
protected $spent = 0.0;
/** @var float */
protected $left = 0.0;
/**
* @return AccountModel
@ -54,5 +57,23 @@ class BalanceEntry
$this->spent = $spent;
}
/**
* @return float
*/
public function getLeft()
{
return $this->left;
}
/**
* @param float $left
*/
public function setLeft($left)
{
$this->left = $left;
}
}

View File

@ -3,6 +3,7 @@
namespace FireflyIII\Helpers\Collection;
use FireflyIII\Models\Budget as BudgetModel;
use FireflyIII\Models\LimitRepetition;
use Illuminate\Support\Collection;
/**
@ -15,14 +16,20 @@ use Illuminate\Support\Collection;
class BalanceLine
{
const ROLE_DEFAULTROLE = 1;
const ROLE_TAGROLE = 2;
const ROLE_DIFFROLE = 3;
/** @var Collection */
protected $balanceEntries;
/** @var BudgetModel */
protected $budget;
/** @var float */
protected $budgetAmount = 0.0;
/** @var LimitRepetition */
protected $repetition;
protected $role = self::ROLE_DEFAULTROLE;
/**
*
@ -40,6 +47,25 @@ class BalanceLine
$this->balanceEntries->push($balanceEntry);
}
/**
* @return string
*/
public function getTitle()
{
if ($this->getBudget() instanceof BudgetModel) {
return $this->getBudget()->name;
}
if ($this->getRole() == self::ROLE_DEFAULTROLE) {
return trans('firefly.noBudget');
}
if ($this->getRole() == self::ROLE_TAGROLE) {
return trans('firefly.coveredWithTags');
}
if ($this->getRole() == self::ROLE_DIFFROLE) {
return trans('firefly.leftUnbalanced');
}
}
/**
* @return BudgetModel
*/
@ -57,11 +83,32 @@ class BalanceLine
}
/**
* @return int
*/
public function getRole()
{
return $this->role;
}
/**
* @param int $role
*/
public function setRole($role)
{
$this->role = $role;
}
/**
* If a BalanceLine has a budget/repetition, each BalanceEntry in this BalanceLine
* should have a "spent" value, which is the amount of money that has been spent
* on the given budget/repetition. If you subtract all those amounts from the budget/repetition's
* total amount, this is returned:
*
* @return float
*/
public function left()
public function leftOfRepetition()
{
$start = $this->getBudgetAmount();
$start = $this->getRepetition() ? $this->getRepetition()->amount : 0;
/** @var BalanceEntry $balanceEntry */
foreach ($this->getBalanceEntries() as $balanceEntry) {
$start += $balanceEntry->getSpent();
@ -71,19 +118,19 @@ class BalanceLine
}
/**
* @return float
* @return LimitRepetition
*/
public function getBudgetAmount()
public function getRepetition()
{
return $this->budgetAmount;
return $this->repetition;
}
/**
* @param float $budgetAmount
* @param LimitRepetition $repetition
*/
public function setBudgetAmount($budgetAmount)
public function setRepetition($repetition)
{
$this->budgetAmount = $budgetAmount;
$this->repetition = $repetition;
}
/**
@ -102,5 +149,22 @@ class BalanceLine
$this->balanceEntries = $balanceEntries;
}
/**
* If the BalanceEntries for a BalanceLine have a "left" value, the amount
* of money left in the entire BalanceLine is returned here:
*
* @return float
*/
public function sumOfLeft()
{
$sum = 0.0;
/** @var BalanceEntry $balanceEntry */
foreach ($this->getBalanceEntries() as $balanceEntry) {
$sum += $balanceEntry->getLeft();
}
return $sum;
}
}

View File

@ -98,8 +98,9 @@ class ReportHelper implements ReportHelperInterface
*/
public function getBalanceReport(Carbon $start, Carbon $end, $shared)
{
$repository = App::make('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$balance = new Balance;
$repository = App::make('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$tagRepository = App::make('FireflyIII\Repositories\Tag\TagRepositoryInterface');
$balance = new Balance;
// build a balance header:
$header = new BalanceHeader;
@ -117,9 +118,7 @@ class ReportHelper implements ReportHelperInterface
// get budget amount for current period:
$rep = $repository->getCurrentRepetition($budget, $start);
if ($rep) {
$line->setBudgetAmount($rep->amount);
}
$line->setRepetition($rep);
// loop accounts:
foreach ($accounts as $account) {
@ -127,7 +126,7 @@ class ReportHelper implements ReportHelperInterface
$balanceEntry->setAccount($account);
// get spent:
$spent = $this->query->spentInBudget($account, $budget, $start, $end, $shared); // I think shared is irrelevant.
$spent = $this->query->spentInBudget($account, $budget, $start, $end); // I think shared is irrelevant.
$balanceEntry->setSpent($spent);
$line->addBalanceEntry($balanceEntry);
@ -137,15 +136,42 @@ class ReportHelper implements ReportHelperInterface
}
// then a new line for without budget.
// and one for the tags:
$empty = new BalanceLine;
$tags = new BalanceLine;
$diffLine = new BalanceLine;
$tags->setRole(BalanceLine::ROLE_TAGROLE);
$diffLine->setRole(BalanceLine::ROLE_DIFFROLE);
foreach ($accounts as $account) {
$spent = $this->query->spentNoBudget($account, $start, $end);
$balanceEntry = new BalanceEntry;
$balanceEntry->setAccount($account);
$balanceEntry->setSpent($spent);
$empty->addBalanceEntry($balanceEntry);
$left = $tagRepository->coveredByBalancingActs($account, $start, $end);
$diff = $spent + $left;
// budget
$budgetEntry = new BalanceEntry;
$budgetEntry->setAccount($account);
$budgetEntry->setSpent($spent);
$empty->addBalanceEntry($budgetEntry);
// balanced by tags
$tagEntry = new BalanceEntry;
$tagEntry->setAccount($account);
$tagEntry->setLeft($left);
$tags->addBalanceEntry($tagEntry);
// difference:
$diffEntry = new BalanceEntry;
$diffEntry->setAccount($account);
$diffEntry->setSpent($diff);
$diffLine->addBalanceEntry($diffEntry);
}
$balance->addBalanceLine($empty);
$balance->addBalanceLine($tags);
$balance->addBalanceLine($diffLine);
$balance->setBalanceHeader($header);

View File

@ -196,11 +196,10 @@ class ReportQuery implements ReportQueryInterface
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param bool $shared
*
* @return float
*/
public function spentInBudget(Account $account, Budget $budget, Carbon $start, Carbon $end, $shared = false)
public function spentInBudget(Account $account, Budget $budget, Carbon $start, Carbon $end)
{
return floatval(

View File

@ -61,11 +61,10 @@ interface ReportQueryInterface
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param bool $shared
*
* @return float
*/
public function spentInBudget(Account $account, Budget $budget, Carbon $start, Carbon $end, $shared = false);
public function spentInBudget(Account $account, Budget $budget, Carbon $start, Carbon $end);
/**
* @param Account $account

View File

@ -289,9 +289,9 @@ class TagController extends Controller
public function update(TagFormRequest $request, TagRepositoryInterface $repository, Tag $tag)
{
if (Input::get('setTag') == 'true') {
$latitude = strlen($request->get('latitude')) > 0 ? $request->get('latitude') : null;
$longitude = strlen($request->get('longitude')) > 0 ? $request->get('longitude') : null;
$zoomLevel = strlen($request->get('zoomLevel')) > 0 ? $request->get('zoomLevel') : null;
$latitude = $request->get('latitude');
$longitude = $request->get('longitude');
$zoomLevel = $request->get('zoomLevel');
} else {
$latitude = null;
$longitude = null;

View File

@ -48,6 +48,7 @@ class TagFormRequest extends Request
'date' => 'date',
'latitude' => 'numeric|min:-90|max:90',
'longitude' => 'numeric|min:-90|max:90',
'zoomLevel' => 'numeric|min:0|max:80',
'tagMode' => 'required|in:nothing,balancingAct,advancePayment'
];
}

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\JoinClause;
use Watson\Validating\ValidatingTrait;
/**
@ -74,7 +75,7 @@ class TransactionJournal extends Model
}
/**
* @return Account|mixed
* @return Account
*/
public function getAssetAccountAttribute()
{
@ -93,9 +94,10 @@ class TransactionJournal extends Model
}
return $this->transactions()->first();
return $this->transactions()->first()->account;
}
/**
* @codeCoverageIgnore
* @return \Illuminate\Database\Eloquent\Relations\HasMany
@ -211,6 +213,24 @@ class TransactionJournal extends Model
return $query->where('date', '=', $date->format('Y-m-d'));
}
/**
* Returns the account to which the money was moved.
*
* @codeCoverageIgnore
*
* @param EloquentBuilder $query
* @param Account $account
*/
public function scopeToAccountIs(EloquentBuilder $query, Account $account)
{
$query->leftJoin(
'transactions', function (JoinClause $join) {
$join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0);
}
);
$query->where('transactions.account_id', $account->id);
}
/**
* @codeCoverageIgnore
*

View File

@ -272,13 +272,10 @@ class BillRepository implements BillRepositoryInterface
*/
public function scan(Bill $bill, TransactionJournal $journal)
{
/*
* Match words.
*/
$amountMatch = false;
$wordMatch = false;
$matches = explode(',', $bill->match);
$description = strtolower($journal->description);
Log::debug('Now scanning ' . $description);
/*
* Attach expense account to description for more narrow matching.
@ -316,7 +313,6 @@ class BillRepository implements BillRepositoryInterface
* Match amount.
*/
$amountMatch = false;
if (count($transactions) > 1) {
$amount = max(floatval($transactions[0]->amount), floatval($transactions[1]->amount));

View File

@ -338,55 +338,31 @@ class JournalRepository implements JournalRepositoryInterface
*/
protected function storeAccounts(TransactionType $type, array $data)
{
$from = null;
$to = null;
$fromAccount = null;
$toAccount = null;
switch ($type->type) {
case 'Withdrawal':
$from = Account::find($data['account_id']);
if (strlen($data['expense_account']) > 0) {
$toType = AccountType::where('type', 'Expense account')->first();
$to = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1]
);
} else {
$toType = AccountType::where('type', 'Cash account')->first();
$to = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]
);
}
list($fromAccount, $toAccount) = $this->storeWithdrawalAccounts($data);
break;
case 'Deposit':
$to = Account::find($data['account_id']);
if (strlen($data['revenue_account']) > 0) {
$fromType = AccountType::where('type', 'Revenue account')->first();
$from = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1]
);
} else {
$toType = AccountType::where('type', 'Cash account')->first();
$from = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]
);
}
list($fromAccount, $toAccount) = $this->storeDepositAccounts($data);
break;
case 'Transfer':
$from = Account::find($data['account_from_id']);
$to = Account::find($data['account_to_id']);
$fromAccount = Account::find($data['account_from_id']);
$toAccount = Account::find($data['account_to_id']);
break;
}
if (is_null($to) || (!is_null($to) && is_null($to->id))) {
if (is_null($toAccount)) {
Log::error('"to"-account is null, so we cannot continue!');
App::abort(500, '"to"-account is null, so we cannot continue!');
// @codeCoverageIgnoreStart
}
// @codeCoverageIgnoreEnd
if (is_null($from) || (!is_null($from) && is_null($from->id))) {
if (is_null($fromAccount)) {
Log::error('"from"-account is null, so we cannot continue!');
App::abort(500, '"from"-account is null, so we cannot continue!');
// @codeCoverageIgnoreStart
@ -394,6 +370,54 @@ class JournalRepository implements JournalRepositoryInterface
// @codeCoverageIgnoreEnd
return [$from, $to];
return [$fromAccount, $toAccount];
}
/**
* @param array $data
*
* @return array
*/
protected function storeWithdrawalAccounts(array $data)
{
$fromAccount = Account::find($data['account_id']);
if (strlen($data['expense_account']) > 0) {
$toType = AccountType::where('type', 'Expense account')->first();
$toAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1]
);
} else {
$toType = AccountType::where('type', 'Cash account')->first();
$toAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]
);
}
return [$fromAccount, $toAccount];
}
/**
* @param array $data
*
* @return array
*/
protected function storeDepositAccounts(array $data)
{
$toAccount = Account::find($data['account_id']);
if (strlen($data['revenue_account']) > 0) {
$fromType = AccountType::where('type', 'Revenue account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1]
);
} else {
$toType = AccountType::where('type', 'Cash account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]
);
}
return [$fromAccount, $toAccount];
}
}

View File

@ -4,6 +4,8 @@ namespace FireflyIII\Repositories\Tag;
use Auth;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@ -17,6 +19,7 @@ use Illuminate\Support\Collection;
class TagRepository implements TagRepositoryInterface
{
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five.
*
@ -52,6 +55,38 @@ class TagRepository implements TagRepositoryInterface
return false;
}
/**
* This method scans the transaction journals from or to the given asset account
* and checks if these are part of a balancing act. If so, it will sum up the amounts
* transferred into the balancing act (if any) and return this amount.
*
* This method effectively tells you the amount of money that has been balanced out
* correctly in the given period for the given account.
*
* @param Account $account
* @param Carbon $start
* @param Carbon $end
*
* @return float
*/
public function coveredByBalancingActs(Account $account, Carbon $start, Carbon $end)
{
// the quickest way to do this is by scanning all balancingAct tags
// because there will be less of them any way.
$tags = Auth::user()->tags()->where('tagMode', 'balancingAct')->get();
$amount = 0;
/** @var Tag $tag */
foreach ($tags as $tag) {
$transfer = $tag->transactionjournals()->after($start)->before($end)->toAccountIs($account)->transactionTypes(['Transfer'])->first();
if ($transfer) {
$amount += $transfer->amount;
}
}
return $amount;
}
/**
* @param Tag $tag
*
@ -63,6 +98,7 @@ class TagRepository implements TagRepositoryInterface
return true;
}
// @codeCoverageIgnoreEnd
/**
* @return Collection
@ -79,7 +115,6 @@ class TagRepository implements TagRepositoryInterface
return $tags;
}
// @codeCoverageIgnoreEnd
/**
* @param array $data
@ -223,6 +258,7 @@ class TagRepository implements TagRepositoryInterface
return true;
}
return false;
}
}

View File

@ -2,6 +2,8 @@
namespace FireflyIII\Repositories\Tag;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
@ -15,6 +17,23 @@ use Illuminate\Support\Collection;
interface TagRepositoryInterface
{
/**
* This method scans the transaction journals from or to the given asset account
* and checks if these are part of a balancing act. If so, it will sum up the amounts
* transferred into the balancing act (if any) and return this amount.
*
* This method effectively tells you the amount of money that has been balanced out
* correctly in the given period for the given account.
*
* @param Account $account
* @param Carbon $start
* @param Carbon $end
*
* @return float
*/
public function coveredByBalancingActs(Account $account, Carbon $start, Carbon $end);
/**
* @param array $data
*

View File

@ -10,31 +10,44 @@
{% for account in balance.getBalanceHeader.getAccounts %}
<th><a href="{{route('accounts.show',account.id)}}">{{ account.name }}</a></th>
{% endfor %}
<th colspan="2">
<th>
{{ 'leftInBudget'|_ }}
</th>
<th>{{ 'sum'|_ }}</th>
</tr>
<!-- make rows -->
{% for balanceLine in balance.getBalanceLines %}
<tr>
{% if balanceLine.getBudget %}
<td>
{% if balanceLine.getBudget %}
<a href="{{ route('budgets.show',balanceLine.getBudget.id) }}">{{ balanceLine.getBudget.name }}</a>
{% else %}
{{ 'noBudget'|_ }}
{% endif %}
<a href="{{ route('budgets.show',balanceLine.getBudget.id) }}">{{ balanceLine.getTitle }}</a>
</td>
<td>{{ balanceLine.getBudgetAmount|formatAmount }}</td>
<td>{{ balanceLine.getRepetition.amount|formatAmount }}</td>
{% else %}
<td colspan="2">{{ balanceLine.getTitle }}</td>
{% endif %}
{% for balanceEntry in balanceLine.getBalanceEntries %}
<td class="text-danger">
<td>
{% if balanceEntry.getSpent != 0 %}
{{ (balanceEntry.getSpent*-1)|formatAmountPlain }}
<span class="text-danger">{{ (balanceEntry.getSpent*-1)|formatAmountPlain }}</span>
{% endif %}
{% if balanceEntry.getLeft != 0 %}
<span class="text-success">{{ (balanceEntry.getLeft)|formatAmountPlain }}</span>
{% endif %}
</td>
{% endfor %}
<td>
{{ balanceLine.left|formatAmount }}
{% if balanceLine.leftOfRepetition != 0 %}
{{ balanceLine.leftOfRepetition|formatAmount }}
{% endif %}
</td>
<td>
{% if balanceLine.sumOfLeft != 0 %}
{{ balanceLine.sumOfLeft|formatAmount }}
{% endif %}
</td>
</tr>
{% endfor %}

View File

@ -9,6 +9,7 @@ use Illuminate\Support\Collection;
use League\FactoryMuffin\Facade as FactoryMuffin;
/**
* @SuppressWarnings(PHPMD.TooManyMethods)
* Class AccountControllerTest
*/
class AccountControllerTest extends TestCase
@ -118,8 +119,8 @@ class AccountControllerTest extends TestCase
$repository->shouldReceive('openingBalanceTransaction')->andReturn($openingBalance);
// create a transaction that will be returned for the opening balance transaction:
$openingBalanceTransaction = FactoryMuffin::create('FireflyIII\Models\Transaction');
$repository->shouldReceive('getFirstTransaction')->andReturn($openingBalanceTransaction);
$opening = FactoryMuffin::create('FireflyIII\Models\Transaction');
$repository->shouldReceive('getFirstTransaction')->andReturn($opening);
// CURRENCY:
$currency = FactoryMuffin::create('FireflyIII\Models\TransactionCurrency');

View File

@ -4,6 +4,7 @@ use Illuminate\Support\Collection;
use League\FactoryMuffin\Facade as FactoryMuffin;
/**
* @SuppressWarnings(PHPMD.TooManyMethods)
* Class BillControllerTest
*/
class BillControllerTest extends TestCase