Some extensions to budgets.

This commit is contained in:
James Cole 2016-04-25 21:37:08 +02:00
parent 7f7d6cf893
commit 22e6ea700f
6 changed files with 183 additions and 130 deletions

View File

@ -160,6 +160,16 @@ class BudgetController extends Controller
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']);
/**
* Warn user if necessary
*/
$range = Preferences::get('viewRange', '1M')->data;
$repeatFreq = Config::get('firefly.range_to_repeat_freq.' . $range);
$userWarning = '';
if (session('is_custom_range', false) === true) {
$userWarning = strval(trans('firefly.warn_range_' . $repeatFreq));
}
/**
* Do some cleanup:
*/
@ -187,7 +197,7 @@ class BudgetController extends Controller
'budgetMaximum', 'periodStart', 'periodEnd',
'period', 'range', 'budgetIncomeTotal',
'defaultCurrency', 'inactive', 'budgets',
'spent', 'budgeted'
'spent', 'budgeted', 'userWarning'
)
);
}

View File

@ -9,6 +9,7 @@ use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Input;
use Log;
use Preferences;
use Route;
use Session;
@ -33,8 +34,17 @@ class HomeController extends Controller
public function dateRange()
{
$start = new Carbon(Input::get('start'));
$end = new Carbon(Input::get('end'));
$start = new Carbon(Input::get('start'));
$end = new Carbon(Input::get('end'));
$label = Input::get('label');
// check if the label is "everything" or "Custom range" which will betray
// a possible problem with the budgets.
if ($label === strval(trans('firefly.everything')) || $label === strval(trans('firefly.customRange'))) {
Session::put('is_custom_range', true);
} else {
Session::put('is_custom_range', false);
}
$diff = $start->diffInDays($end);

View File

@ -4,6 +4,7 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon;
use Config;
use DB;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
@ -19,6 +20,7 @@ use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Input;
use Preferences;
/**
* Class BudgetRepository
@ -554,6 +556,54 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
return $paginator;
}
/**
* Returns a list of budget limits that are valid in the current given range.
* $ignore is optional. Send an empty limit rep.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param LimitRepetition $ignore
*
* @return Collection
*/
public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection
{
$query = $budget->limitrepetitions()
->where( // valid when either of these are true:
function ($q) use ($start, $end) {
$q->where(
function ($query) use ($start, $end) {
// starts before start time, and the end also after start time.
$query->where('limit_repetitions.startdate', '<=', $start->format('Y-m-d 00:00:00'));
$query->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00'));
}
);
$q->orWhere(
function ($query) use ($start, $end) {
// end after end time, and start is before end time
$query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'));
$query->where('limit_repetitions.enddate', '>=', $end->format('Y-m-d 00:00:00'));
}
);
// start is after start and end is before end
$q->orWhere(
function ($query) use ($start, $end) {
// end after end time, and start is before end time
$query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'));
$query->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d 00:00:00'));
}
);
}
);
if (!is_null($ignore->id)) {
$query->where('limit_repetitions.id', '!=', $ignore->id);
}
$data = $query->get(['limit_repetitions.*']);
return $data;
}
/**
* @param Carbon $start
* @param Carbon $end
@ -819,9 +869,12 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
*/
public function updateLimitAmount(Budget $budget, Carbon $date, int $amount): BudgetLimit
{
// there should be a budget limit for this startdate:
// there might be a budget limit for this startdate:
$viewRange = Preferences::get('viewRange', '1M')->data;
$repeatFreq = Config::get('firefly.range_to_repeat_freq.' . $viewRange);
/** @var BudgetLimit $limit */
$limit = $budget->budgetlimits()->where('budget_limits.startdate', $date)->first(['budget_limits.*']);
$limit = $budget->budgetlimits()->where('budget_limits.startdate', $date)->where('budget_limits.repeat_freq', $repeatFreq)->first(['budget_limits.*']);
if (!$limit) {
// if not, create one!
@ -829,12 +882,13 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
$limit->budget()->associate($budget);
$limit->startdate = $date;
$limit->amount = $amount;
$limit->repeat_freq = 'monthly';
$limit->repeat_freq = $repeatFreq;
$limit->repeats = 0;
$limit->save();
// likewise, there should be a limit repetition to match the end date
// (which is always the end of the month) but that is caught by an event.
// so handled automatically.
} else {
if ($amount > 0) {
@ -847,52 +901,4 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn
return $limit;
}
/**
* Returns a list of budget limits that are valid in the current given range.
* $ignore is optional. Send an empty limit rep.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param LimitRepetition $ignore
*
* @return Collection
*/
public function getValidRepetitions(Budget $budget, Carbon $start, Carbon $end, LimitRepetition $ignore) : Collection
{
$query = $budget->limitrepetitions()
->where( // valid when either of these are true:
function ($q) use ($start, $end) {
$q->where(
function ($query) use ($start, $end) {
// starts before start time, and the end also after start time.
$query->where('limit_repetitions.startdate', '<=', $start->format('Y-m-d 00:00:00'));
$query->where('limit_repetitions.enddate', '>=', $start->format('Y-m-d 00:00:00'));
}
);
$q->orWhere(
function ($query) use ($start, $end) {
// end after end time, and start is before end time
$query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d 00:00:00'));
$query->where('limit_repetitions.enddate', '>=', $end->format('Y-m-d 00:00:00'));
}
);
// start is after start and end is before end
$q->orWhere(
function ($query) use ($start, $end) {
// end after end time, and start is before end time
$query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d 00:00:00'));
$query->where('limit_repetitions.enddate', '<=', $end->format('Y-m-d 00:00:00'));
}
);
}
);
if (!is_null($ignore->id)) {
$query->where('limit_repetitions.id', '!=', $ignore->id);
}
$data = $query->get(['limit_repetitions.*']);
return $data;
}
}

View File

@ -257,6 +257,7 @@ return [
'pref_1M' => 'One month',
'pref_3M' => 'Three months (quarter)',
'pref_6M' => 'Six months',
'pref_1Y' => 'One year',
'pref_languages' => 'Languages',
'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?',
'pref_custom_fiscal_year' => 'Fiscal year settings',
@ -476,6 +477,12 @@ return [
'update_amount' => 'Update amount',
'update_budget' => 'Update budget',
'update_budget_amount_range' => 'Update (expected) available amount between :start and :end',
'warn_range_weekly' => 'You have selected a custom date range using the top right date range selection tool. Your preferences indicate you prefer a "weekly" view. Changes to the budgets below will be valid for exactly one week from now.',
'warn_range_monthly' => 'You have selected a custom date range using the top right date range selection tool. Your preferences indicate you prefer a "monthly" view. Changes to the budgets below will be valid for exactly one month from now.',
'warn_range_quarterly' => 'You have selected a custom date range using the top right date range selection tool. Your preferences indicate you prefer a "quarter" view. Changes to the budgets below will be valid for exactly three months from now.',
'warn_range_half-year' => 'You have selected a custom date range using the top right date range selection tool. Your preferences indicate you prefer the "six months" overview. Changes to the budgets below will be valid for exactly six months from now.',
'warn_range_custom' => 'You have selected a custom date range using the top right date range selection tool. Your preferences indicate you prefer a custom range. Changes to the budgets below will be valid for your custom range.',
'warn_range_yearly' => 'You have selected a custom date range using the top right date range selection tool. Your preferences indicate you prefer the "yearly" overview. Changes to the budgets below will be valid for exactly one year from now.',
// bills:
'matching_on' => 'Matching on',

View File

@ -99,52 +99,65 @@
</div>
</div>
<div class="row">
{% for budget in budgets %}
<div class="col-lg-4 col-sm-6 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
<!-- link in header -->
{% if budget.currentRep.id %}
<a href="{{ route('budgets.show', [budget.id, budget.currentRep.id]) }}" class="budget-link"
data-id="{{ budget.id }}">{{ budget.name }}</a>
{% else %}
<a href="{{ route('budgets.show',budget.id) }}" class="budget-link" data-id="{{ budget.id }}">{{ budget.name }}</a>
{% endif %}
</h3>
{% if userWarning != "" %}
<div class="row">
<div class="col-lg-12">
<p class="well">
<span class="text-danger">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
{{ userWarning }}
</span>
</p>
</div>
</div>
{% endif %}
<!-- ACTIONS MENU -->
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
<div class="btn-group">
<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"><i class="fa fa-ellipsis-v"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="{{ route('budgets.edit',budget.id) }}"><i class="fa fa-pencil fa-fw"></i> {{ 'edit'|_ }}</a></li>
<li><a href="{{ route('budgets.delete',budget.id) }}"><i class="fa fa-trash fa-fw"></i> {{ 'delete'|_ }}</a></li>
</ul>
</div>
<div class="row">
{% for budget in budgets %}
<div class="col-lg-4 col-sm-6 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
<!-- link in header -->
{% if budget.currentRep.id %}
<a href="{{ route('budgets.show', [budget.id, budget.currentRep.id]) }}" class="budget-link"
data-id="{{ budget.id }}">{{ budget.name }}</a>
{% else %}
<a href="{{ route('budgets.show',budget.id) }}" class="budget-link" data-id="{{ budget.id }}">{{ budget.name }}</a>
{% endif %}
</h3>
<!-- ACTIONS MENU -->
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
<div class="btn-group">
<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"><i class="fa fa-ellipsis-v"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="{{ route('budgets.edit',budget.id) }}"><i class="fa fa-pencil fa-fw"></i> {{ 'edit'|_ }}</a></li>
<li><a href="{{ route('budgets.delete',budget.id) }}"><i class="fa fa-trash fa-fw"></i> {{ 'delete'|_ }}</a></li>
</ul>
</div>
</div>
<div class="box-body">
<table class="table">
<tr>
<td style="width:40%;">
{{ 'budgeted'|_ }}
<span class="small"><br/>
{{ session('start').formatLocalized(monthAndDayFormat) }} -
{{ session('end').formatLocalized(monthAndDayFormat) }}</span>
</td>
<td>
<div class="form-group" style="margin-bottom:0;">
<div class="input-group">
<div class="input-group-addon">{{ defaultCurrency.symbol|raw }}</div>
<input type="hidden" name="balance_currency_id" value="1"/>
<input class="form-control budgetAmount" data-original="{{ budget.currentRep.amount|number_format(0,'','') }}"
data-id="{{ budget.id }}" value="{{ budget.currentRep.amount|number_format(0,'','') }}" autocomplete="off"
step="1" min="0" max="{{ budgetMaximum }}" name="amount" type="number">
</div>
<!--
</div>
<div class="box-body">
<table class="table">
<tr>
<td style="width:40%;">
{{ 'budgeted'|_ }}
<span class="small"><br/>
{{ session('start').formatLocalized(monthAndDayFormat) }} -
{{ session('end').formatLocalized(monthAndDayFormat) }}</span>
</td>
<td>
<div class="form-group" style="margin-bottom:0;">
<div class="input-group">
<div class="input-group-addon">{{ defaultCurrency.symbol|raw }}</div>
<input type="hidden" name="balance_currency_id" value="1"/>
<input class="form-control budgetAmount" data-original="{{ budget.currentRep.amount|number_format(0,'','') }}"
data-id="{{ budget.id }}" value="{{ budget.currentRep.amount|number_format(0,'','') }}" autocomplete="off"
step="1" min="0" max="{{ budgetMaximum }}" name="amount" type="number">
</div>
<!--
<div class="small">
<ul class="list-inline">
<li>Previously budgeted:</li>
@ -153,45 +166,45 @@
</ul>
</div>
-->
</div>
</td>
</tr>
<tr>
<td style="width:40%;">
{{ 'spent'|_ }}
<span class="small"><br/>
{{ session('start').formatLocalized(monthAndDayFormat) }} -
{{ session('end').formatLocalized(monthAndDayFormat) }}
</div>
</td>
</tr>
<tr>
<td style="width:40%;">
{{ 'spent'|_ }}
<span class="small"><br/>
{{ session('start').formatLocalized(monthAndDayFormat) }} -
{{ session('end').formatLocalized(monthAndDayFormat) }}
</span>
</td>
<td>{{ budget.spent|formatAmount }}</a></td>
</tr>
{% if budget.otherRepetitions.count > 0 %}
<tr>
<td colspan="2">
<ul class="list-unstyled">
{% for other in budget.otherRepetitions %}
{% if other.id != budget.currentRep.id %}
<li>Budgeted
<a href="{{ route('budgets.show', [budget.id, other.id]) }}">{{ other.amount|formatAmountPlain }}</a>
between
{{ other.startdate.formatLocalized(monthAndDayFormat) }}
and {{ other.enddate.formatLocalized(monthAndDayFormat) }}.
</li>
{% endif %}
{% endfor %}
</ul>
</td>
<td>{{ budget.spent|formatAmount }}</a></td>
</tr>
{% if budget.otherRepetitions.count > 0 %}
<tr>
<td colspan="2">
<ul class="list-unstyled">
{% for other in budget.otherRepetitions %}
{% if other.id != budget.currentRep.id %}
<li>Budgeted
<a href="{{ route('budgets.show', [budget.id, other.id]) }}">{{ other.amount|formatAmountPlain }}</a>
between
{{ other.startdate.formatLocalized(monthAndDayFormat) }}
and {{ other.enddate.formatLocalized(monthAndDayFormat) }}.
</li>
{% endif %}
{% endfor %}
</ul>
</td>
</tr>
{% endif %}
</table>
</div>
{% endif %}
</table>
</div>
</div>
{% if loop.index % 3 == 0 %}
</div>
{% if loop.index % 3 == 0 %}
</div><div class="row">
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% if inactive|length > 0 %}

View File

@ -107,6 +107,13 @@
{{ 'pref_6M'|_ }}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="viewRange"
value="1Y" {% if viewRange == '1Y' %} checked {% endif %}>
{{ 'pref_1Y'|_ }}
</label>
</div>
</div>
</div>
</div>