monthly budget, monthly report, unified report calculation (#2684)
This commit is contained in:
@@ -6,25 +6,15 @@
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.recordAmount > 0) %}
|
||||
{% set inUse = (stats.counter > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%activity%': activity.name,
|
||||
'%project%': '-',
|
||||
'%customer%': '-',
|
||||
'%records%': stats.recordAmount,
|
||||
'%duration%': stats.recordDuration|duration
|
||||
'%records%': stats.counter,
|
||||
'%duration%': stats.duration|duration
|
||||
} %}
|
||||
|
||||
{% if activity.project is not null %}
|
||||
{% set params = params|merge({
|
||||
'%project%': activity.project.name,
|
||||
'%customer%': activity.project.customer.name,
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', {
|
||||
'message': ("admin_activity.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'message': ("delete_warning.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'form': form,
|
||||
'used': inUse,
|
||||
'back': path('admin_activity')
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
{% endembed %}
|
||||
|
||||
{% if stats is not null %}
|
||||
{% set currency = null %}
|
||||
{% set currency = kimai_config.getCustomerDefaultCurrency() %}
|
||||
{% if activity.project is not null %}
|
||||
{% set currency = activity.project.customer.currency %}
|
||||
{% endif %}
|
||||
|
||||
@@ -30,12 +30,15 @@
|
||||
{% endif %}
|
||||
{% if form.budget is defined %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.budget) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.timeBudget) }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.budgetType) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_row(form.visible) }}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
{% block page_content_before %}
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_BEFORE')) %}
|
||||
{{ event.content|raw }}
|
||||
<div class="toolbar-pad no-print">
|
||||
<div class="no-print">
|
||||
{% block main_before %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,18 +6,15 @@
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.recordAmount > 0) %}
|
||||
{% set inUse = (stats.counter > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%activity%': stats.activityAmount,
|
||||
'%project%': stats.projectAmount,
|
||||
'%customer%': customer.name,
|
||||
'%records%': stats.recordAmount,
|
||||
'%duration%': stats.recordDuration|duration
|
||||
'%records%': stats.counter,
|
||||
'%duration%': stats.duration|duration
|
||||
} %}
|
||||
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', {
|
||||
'message': ("admin_customer.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'message': ("delete_warning.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'form': form,
|
||||
'used': inUse,
|
||||
'back': path('admin_customer')
|
||||
|
||||
@@ -69,12 +69,15 @@
|
||||
</div>
|
||||
{% if form.budget is defined %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.budget) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.timeBudget) }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.budgetType) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_row(form.visible) }}
|
||||
|
||||
@@ -2,34 +2,153 @@
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ widgets.label(entity.timeBudget|duration, 'gray') }}
|
||||
{% endif %}
|
||||
{{ 'label.timeBudget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'label.budgetType_month'|trans }}){% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_attributes %}id="time_budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ progress.progressbar(stats.recordDuration, stats.durationBillable, 'label.billable'|trans, stats.durationBillable|duration ~ ' / ' ~ stats.recordDuration|duration, true) }}
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ progress.progressbar(entity.timeBudget, stats.durationBillable, 'label.timeBudget'|trans, stats.durationBillable|duration ~ ' / ' ~ entity.timeBudget|duration) }}
|
||||
{% endif %}
|
||||
{% set durationTrans = entity.isMonthlyBudget() ? 'stats.durationMonth' : 'stats.durationTotal' %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-5">
|
||||
<table class="table table-hover dataTable">
|
||||
<tr>
|
||||
<td>{{ 'label.timeBudget'|trans }}</td>
|
||||
<td class="text-nowrap text-right">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ entity.timeBudget|duration }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap text-right">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
100%
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% set totalPercentReached = 100 %}
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{% set totalPercentReached = (stats.durationBillable / (entity.timeBudget / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ durationTrans|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.duration|duration }}</td>
|
||||
<td class="text-nowrap text-right">{{ totalPercentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% set percentReached = 0 %}
|
||||
{% if stats.duration > 0 %}
|
||||
{% set percentReached = (stats.durationBillable / (stats.duration / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ 'label.billable'|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.durationBillable|duration }}</td>
|
||||
<td class="text-nowrap text-right">{{ percentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% if entity.timeBudget > 0 %}
|
||||
<tr>
|
||||
<th colspan="3" class="text-nowrap text-right">
|
||||
{% if totalPercentReached < 100 %}
|
||||
{{ 'stats.percentUsedLeft'|trans({'%percent%': totalPercentReached|number_format(2), '%left%': (entity.timeBudget - stats.durationBillable)|duration}) }}
|
||||
{% else %}
|
||||
{{ 'stats.percentUsed'|trans({'%percent%': totalPercentReached|number_format(2)}) }}
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-7">
|
||||
<div class="box-padding">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ progress.progressbar(entity.timeBudget, stats.durationBillable, durationTrans|trans, stats.durationBillable|duration ~ ' / ' ~ entity.timeBudget|duration) }}
|
||||
{% endif %}
|
||||
{{ progress.progressbar(stats.duration, stats.durationBillable, 'label.billable'|trans, stats.durationBillable|duration ~ ' / ' ~ stats.duration|duration, true) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
{{ 'label.budget'|trans }}
|
||||
{% if entity.budget > 0 %}
|
||||
{{ widgets.label(entity.budget|money(currency), 'gray') }}
|
||||
{% endif %}
|
||||
{{ 'label.budget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'label.budgetType_month'|trans }}){% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_attributes %}id="budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ progress.progressbar(stats.recordRate, stats.rateBillable, 'label.billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.recordRate|money(currency), true) }}
|
||||
{% if entity.budget > 0 %}
|
||||
{{ progress.progressbar(entity.budget, stats.rateBillable, 'label.budget'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}
|
||||
{% endif %}
|
||||
{{ progress.progressbar(stats.rateBillable, stats.recordInternalRate, 'label.rate_internal'|trans, stats.recordInternalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}
|
||||
{% set rateTrans = entity.isMonthlyBudget() ? 'stats.amountMonth' : 'stats.amountTotal' %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-5">
|
||||
<table class="table table-hover dataTable">
|
||||
<tr>
|
||||
<td>{{ 'label.budget'|trans }}</td>
|
||||
<td class="text-nowrap text-right">
|
||||
{% if entity.budget > 0 %}
|
||||
{{ entity.budget|money(currency) }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap text-right">
|
||||
{% if entity.budget > 0 %}
|
||||
100%
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% set totalPercentReached = 100 %}
|
||||
{% if entity.budget > 0 %}
|
||||
{% set totalPercentReached = (stats.rateBillable / (entity.budget / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ rateTrans|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.rate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">{{ totalPercentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% set percentReached = 0 %}
|
||||
{% if stats.rate > 0 %}
|
||||
{% set percentReached = (stats.rateBillable / (stats.rate / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ 'label.billable'|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.rateBillable|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">{{ percentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% set percentReached = 0 %}
|
||||
{% if stats.rate > 0 %}
|
||||
{% set percentReached = (stats.internalRate / (stats.rateBillable / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ 'label.rate_internal'|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.internalRate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">{{ percentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% if entity.budget > 0 %}
|
||||
<tr>
|
||||
<th colspan="3" class="text-nowrap text-right">
|
||||
{% if totalPercentReached < 100 %}
|
||||
{{ 'stats.percentUsedLeft'|trans({'%percent%': totalPercentReached|number_format(2), '%left%': (entity.timeBudget - stats.rateBillable)|money(currency)}) }}
|
||||
{% else %}
|
||||
{{ 'stats.percentUsed'|trans({'%percent%': totalPercentReached|number_format(2)}) }}
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-7">
|
||||
<div class="box-padding">
|
||||
{% if entity.budget > 0 %}
|
||||
{{ progress.progressbar(entity.budget, stats.rateBillable, rateTrans|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}
|
||||
{% endif %}
|
||||
{{ progress.progressbar(stats.rate, stats.rateBillable, 'label.billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.rate|money(currency), true) }}
|
||||
{{ progress.progressbar(stats.rateBillable, stats.internalRate, 'label.rate_internal'|trans, stats.internalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
@@ -5,16 +5,6 @@
|
||||
{% block document_title %}{{ 'export.title'|trans }}{% endblock %}
|
||||
|
||||
{% set storageItemName = 'kimai_html_export' %}
|
||||
{% set showRateBudget = false %}
|
||||
{% set showTimeBudget = false %}
|
||||
{% for budget in budgets %}
|
||||
{% if budget.money_left > 0 %}
|
||||
{% set showRateBudget = true %}
|
||||
{% endif %}
|
||||
{% if budget.time_left > 0 %}
|
||||
{% set showTimeBudget = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set decimal = decimal|default(false) %}
|
||||
{% set columnTitles = {} %}
|
||||
@@ -105,7 +95,6 @@
|
||||
document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
|
||||
saveVisibility();
|
||||
});
|
||||
{% if showTimeBudget %}
|
||||
document.getElementById('summary-timeBudget').addEventListener('click', function(event) {
|
||||
var cells = document.getElementsByClassName('export-timeBudget');
|
||||
for (var columnCell of cells) {
|
||||
@@ -113,8 +102,6 @@
|
||||
}
|
||||
saveVisibility();
|
||||
});
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
document.getElementById('summary-budget').addEventListener('click', function(event) {
|
||||
var cells = document.getElementsByClassName('export-budget');
|
||||
for (var columnCell of cells) {
|
||||
@@ -122,7 +109,6 @@
|
||||
}
|
||||
saveVisibility();
|
||||
});
|
||||
{% endif %}
|
||||
document.getElementById('summary-duration').addEventListener('click', function(event) {
|
||||
var cells = document.getElementsByClassName('summary-duration');
|
||||
for (var columnCell of cells) {
|
||||
@@ -360,22 +346,18 @@
|
||||
{{ 'label.activity'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
{% if showTimeBudget %}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-timeBudget">
|
||||
<input type="checkbox" id="summary-timeBudget" name="summary-timeBudget" checked>
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-budget">
|
||||
<input type="checkbox" id="summary-budget" name="summary-budget" checked>
|
||||
{{ 'label.budget'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-duration">
|
||||
@@ -438,12 +420,8 @@
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
{% if showTimeBudget %}
|
||||
<th class="center export-timeBudget">{{ 'label.timeBudget'|trans }}</th>
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
<th class="center export-budget">{{ 'label.budget'|trans }}</th>
|
||||
{% endif %}
|
||||
<th class="center export-timeBudget">{{ 'label.timeBudget'|trans }}</th>
|
||||
<th class="center export-budget">{{ 'label.budget'|trans }}</th>
|
||||
<th class="duration summary-duration">{{ 'label.duration'|trans }}</th>
|
||||
<th class="cost summary-rate-internal">{{ 'label.rate_internal'|trans }}</th>
|
||||
<th class="cost summary-rate">{{ 'label.rate'|trans }}</th>
|
||||
@@ -463,12 +441,8 @@
|
||||
{% if customer is not same as(summary.customer) %}
|
||||
<tr class="summary">
|
||||
<td colspan="2"> </td>
|
||||
{% if showTimeBudget %}
|
||||
<td class="export-timeBudget"></td>
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
<td class="export-budget"></td>
|
||||
{% endif %}
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
</td>
|
||||
@@ -484,20 +458,16 @@
|
||||
<tr>
|
||||
<td>{{ summary.customer }}</td>
|
||||
<td>{{ summary.project }}</td>
|
||||
{% if showTimeBudget %}
|
||||
<td class="center export-timeBudget">
|
||||
{% if budgets[id] is defined and budgets[id].time_left > 0 %}
|
||||
<span class="duration-format" data-duration="{{ budgets[id].time_left }}">{{ budgets[id].time_left|duration(decimal) }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
<td class="center export-budget">
|
||||
{% if budgets[id] is defined and budgets[id].money_left > 0 %}
|
||||
{{ budgets[id].money_left|money(summary.currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="center export-timeBudget">
|
||||
{% if budgets[id] is defined and budgets[id].time_left > 0 %}
|
||||
<span class="duration-format" data-duration="{{ budgets[id].time_left }}">{{ budgets[id].time_left|duration(decimal) }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center export-budget">
|
||||
{% if budgets[id] is defined and budgets[id].money_left > 0 %}
|
||||
{{ budgets[id].money_left|money(summary.currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ summary.duration }}">{{ summary.duration|duration(decimal) }}</span>
|
||||
</td>
|
||||
@@ -511,12 +481,8 @@
|
||||
{% if customer is not same as(null) %}
|
||||
<tr class="summary">
|
||||
<td colspan="2"> </td>
|
||||
{% if showTimeBudget %}
|
||||
<td class="export-timeBudget"></td>
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
<td class="export-budget"></td>
|
||||
{% endif %}
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
</td>
|
||||
@@ -539,6 +505,8 @@
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<th>{{ 'label.activity'|trans }}</th>
|
||||
<th class="center export-timeBudget">{{ 'label.timeBudget'|trans }}</th>
|
||||
<th class="center export-budget">{{ 'label.budget'|trans }}</th>
|
||||
<th class="duration summary-duration">{{ 'label.duration'|trans }}</th>
|
||||
<th class="cost summary-rate-internal">{{ 'label.rate_internal'|trans }}</th>
|
||||
<th class="cost summary-rate">{{ 'label.rate'|trans }}</th>
|
||||
@@ -550,7 +518,7 @@
|
||||
{% set customerRate = 0 %}
|
||||
{% set customerInternalRate = 0 %}
|
||||
{% set customerCurrency = null %}
|
||||
{% for summary in summaries %}
|
||||
{% for id, summary in summaries %}
|
||||
{% if customer is same as(null) %}
|
||||
{% set customer = summary.customer %}
|
||||
{% set customerCurrency = summary.currency %}
|
||||
@@ -558,6 +526,8 @@
|
||||
{% if customer is not same as(summary.customer) %}
|
||||
<tr class="summary">
|
||||
<td colspan="3"></td>
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
</td>
|
||||
@@ -570,11 +540,26 @@
|
||||
{% set customerRate = 0 %}
|
||||
{% set customerInternalRate = 0 %}
|
||||
{% endif %}
|
||||
{% for activitySummary in summary.activities %}
|
||||
{% for activityId, activitySummary in summary.activities %}
|
||||
<tr>
|
||||
<td>{{ summary.customer }}</td>
|
||||
<td>{{ summary.project }}</td>
|
||||
<td>{{ activitySummary.activity }}</td>
|
||||
{% if activity_budgets[id] is defined and activity_budgets[id][activityId] is defined %}
|
||||
<td class="center export-timeBudget">
|
||||
{% if activity_budgets[id][activityId].time_left > 0 %}
|
||||
<span class="duration-format" data-duration="{{ activity_budgets[id][activityId].time_left }}">{{ activity_budgets[id][activityId].time_left|duration(decimal) }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center export-budget">
|
||||
{% if activity_budgets[id][activityId].money_left > 0 %}
|
||||
{{ activity_budgets[id][activityId].money_left|money(summary.currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="center export-timeBudget"></td>
|
||||
<td class="center export-budget"></td>
|
||||
{% endif %}
|
||||
<td class="duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ activitySummary.duration }}">{{ activitySummary.duration|duration(decimal) }}</span>
|
||||
</td>
|
||||
@@ -589,6 +574,8 @@
|
||||
{% if customer is not same as(null) %}
|
||||
<tr class="summary">
|
||||
<td colspan="3"></td>
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
</td>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
{% block yearpicker_widget -%}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="$('#{{ form.vars.id }}').val('{{ previousYear|date_short }}').change()" data-toggle="tooltip" data-placement="top" title="{{ previousYear|date_short }}">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="$('#{{ form.vars.id }}').val('{{ previousYear|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ previousYear|date_short }}">
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
@@ -85,7 +85,7 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{ nextYear|date_short }}').change()" data-toggle="tooltip" data-placement="top" title="{{ nextYear|date_short }}">
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{ nextYear|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ nextYear|date_short }}">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -94,13 +94,13 @@
|
||||
|
||||
{% block monthpicker_widget -%}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="$('#{{ form.vars.id }}').val('{{ previousMonth|date_short }}').change()" data-toggle="tooltip" data-placement="top" title="{{ previousMonth|month_name(true) }}">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="$('#{{ form.vars.id }}').val('{{ previousMonth|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ previousMonth|month_name(true) }}">
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_month_name">{{ month|month_name(true) }}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{ nextMonth|date_short }}').change()" data-toggle="tooltip" data-placement="top" title="{{ nextMonth|month_name(true) }}">
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{ nextMonth|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ nextMonth|month_name(true) }}">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -109,13 +109,13 @@
|
||||
|
||||
{% block weekpicker_widget -%}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="$('#{{ form.vars.id }}').val('{{ previousWeek|date_short }}').change()" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': previousWeek|date_format('W')}) }}">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="$('#{{ form.vars.id }}').val('{{ previousWeek|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': previousWeek|date_format('W')}) }}">
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_week_number">{{ 'stats.workingTimeWeek'|trans({'%week%': week|date_format('W')}) }}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{ nextWeek|date_short }}').change()" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': nextWeek|date_format('W')}) }}">
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{ nextWeek|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': nextWeek|date_format('W')}) }}">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -109,6 +109,9 @@
|
||||
{# change the next if, once data_table_header_options() will be deleted #}
|
||||
{% if classes is iterable %}
|
||||
{% set classes = classes.class %}
|
||||
{% if columns[column].columnClass is defined %}
|
||||
{% set classes = classes ~ ' ' ~ columns[column].columnClass %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'alwaysVisible' in classes %}
|
||||
{# as this column should always be visible, we remove every class that includes hidden #}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
{% set formatter = options['format'] ?? null %}
|
||||
{% set currency = options['currency'] ?? null %}
|
||||
{% set percentDecimals = options['decimals'] ?? 1 %}
|
||||
{% set transPercentOpen = options['trans_percent_open'] ?? 'stats.percentUsedLeft' %}
|
||||
{% set transPercentSpent = options['trans_percent_spent'] ?? 'stats.percentUsed' %}
|
||||
{% if formatter == 'duration' %}
|
||||
{% set currentFormatted = current|duration %}
|
||||
{% set maxFormatted = max|duration %}
|
||||
@@ -44,9 +46,9 @@
|
||||
<div class="progress-bar {{ progressbar_color(percentReached, reverseColors) }}" role="progressbar" aria-valuenow="{{ width }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ width }}%"></div>
|
||||
</div>
|
||||
{% if open is not null and open > 0 %}
|
||||
<small>{{ 'stats.percentUsedLeft'|trans({'%percent%': percentReached|number_format(percentDecimals), '%left%': openFormatted}) }}</small>
|
||||
<small>{{ transPercentOpen|trans({'%percent%': percentReached|number_format(percentDecimals), '%left%': openFormatted}) }}</small>
|
||||
{% else %}
|
||||
<small>{{ 'stats.percentUsed'|trans({'%percent%': percentReached|number_format(percentDecimals)}) }}</small>
|
||||
<small>{{ transPercentSpent|trans({'%percent%': percentReached|number_format(percentDecimals)}) }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -104,3 +106,47 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@param \App\Model\BudgetStatisticModelInterface budgetStatisticModel
|
||||
@param string currency
|
||||
#}
|
||||
{% macro progressbar_budget(budgetStatisticModel, currency) %}
|
||||
{% if budgetStatisticModel.hasBudget() %}
|
||||
{% set options = {
|
||||
'max': budgetStatisticModel.getBudget(),
|
||||
'current': budgetStatisticModel.getBudgetSpent(),
|
||||
'open': (budgetStatisticModel.getBudgetOpen()),
|
||||
'format': 'money',
|
||||
'currency': currency,
|
||||
} %}
|
||||
{% if budgetStatisticModel.isMonthlyBudget() %}
|
||||
{% set options = options|merge({
|
||||
'trans_percent_open': 'stats.percentUsedLeft_month',
|
||||
'trans_percent_spent': 'stats.percentUsed_month',
|
||||
}) %}
|
||||
{% endif %}
|
||||
{{ _self.progressbar_full(options) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@param \App\Model\BudgetStatisticModelInterface budgetStatisticModel
|
||||
#}
|
||||
{% macro progressbar_timebudget(budgetStatisticModel) %}
|
||||
{% if budgetStatisticModel.hasTimeBudget() %}
|
||||
{% set options = {
|
||||
'max': budgetStatisticModel.getTimeBudget(),
|
||||
'current': budgetStatisticModel.getTimeBudgetSpent(),
|
||||
'open': (budgetStatisticModel.getTimeBudgetOpen()),
|
||||
'format': 'duration',
|
||||
} %}
|
||||
{% if budgetStatisticModel.isMonthlyBudget() %}
|
||||
{% set options = options|merge({
|
||||
'trans_percent_open': 'stats.percentUsedLeft_month',
|
||||
'trans_percent_spent': 'stats.percentUsed_month',
|
||||
}) %}
|
||||
{% endif %}
|
||||
{{ _self.progressbar_full(options) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -504,7 +504,7 @@
|
||||
{% if showTitle %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.team'|trans }}</th>
|
||||
<th class="hw-min">{{ 'label.team'|trans }}</th>
|
||||
<th>{{ 'label.user'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -515,7 +515,7 @@
|
||||
{% set userTeamCount = users|length %}
|
||||
<tr{% if is_granted('edit', team) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_team_member', {'id': team.id}) }}"{% endif %}>
|
||||
<td>
|
||||
{{ team.name }}
|
||||
{{ _self.label_team(team) }}
|
||||
</td>
|
||||
<td class="avatars">
|
||||
{{ _self.user_avatar(team.teamlead, ('label.teamlead'|trans ~ ': ' ~ team.teamlead.displayName), 'teamlead') }}
|
||||
@@ -655,3 +655,11 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro percent(maximum, current) %}
|
||||
{% if maximum > 0 %}
|
||||
{{ (current / (maximum / 100))|number_format(2) }} %
|
||||
{% else %}
|
||||
{{ 0|number_format(2) }} %
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -6,18 +6,15 @@
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.recordAmount > 0) %}
|
||||
{% set inUse = (stats.counter > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%project%': project.name,
|
||||
'%customer%': project.customer.name,
|
||||
'%records%': stats.recordAmount,
|
||||
'%activities%': stats.activityAmount,
|
||||
'%duration%': stats.recordDuration|duration
|
||||
'%records%': stats.counter,
|
||||
'%duration%': stats.duration|duration
|
||||
} %}
|
||||
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', {
|
||||
'message': ("admin_project.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'message': ("delete_warning.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'form': form,
|
||||
'used': inUse,
|
||||
'back': path('admin_project')
|
||||
|
||||
@@ -49,42 +49,66 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% if is_granted('details', project) %}
|
||||
{% if project.orderNumber is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.orderNumber'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ project.orderNumber }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.orderDate is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.orderDate'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{% if project.orderDate is not empty %}
|
||||
{{ project.orderDate|date_full }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{{ project.orderDate|date_full }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.start is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.project_start'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{% if project.start is not empty %}
|
||||
{{ project.start|date_full }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{{ project.start|date_full }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.end is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.project_end'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{% if project.end is not empty %}
|
||||
{{ project.end|date_full }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{{ project.end|date_full }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', project) %}
|
||||
{% if project.hasBudget() %}
|
||||
<tr>
|
||||
<th>
|
||||
{{ 'label.budget'|trans }}
|
||||
{% if project.isMonthlyBudget() %}
|
||||
({{ 'label.budgetType_month'|trans }})
|
||||
{% endif %}
|
||||
</th>
|
||||
<td colspan="3">
|
||||
{{ project.getBudget()|money(project.customer.currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.hasTimeBudget() %}
|
||||
<tr>
|
||||
<th>
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
{% if project.isMonthlyBudget() %}
|
||||
({{ 'label.budgetType_month'|trans }})
|
||||
{% endif %}
|
||||
</th>
|
||||
<td colspan="3">
|
||||
{{ project.getTimeBudget()|duration }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% for metaField in project.visibleMetaFields|sort((a, b) => a.order <=> b.order) %}
|
||||
<tr>
|
||||
|
||||
@@ -41,12 +41,15 @@
|
||||
</div>
|
||||
{% if form.budget is defined %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.budget) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.timeBudget) }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.budgetType) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_row(form.visible) }}
|
||||
|
||||
97
templates/reporting/project_daterange.html.twig
Normal file
97
templates/reporting/project_daterange.html.twig
Normal file
@@ -0,0 +1,97 @@
|
||||
{% extends 'reporting/layout.html.twig' %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% block report_title %}{{ 'report_project_daterange'|trans({}, 'reporting') }}{% endblock %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'timeBudget': {'class': 'hidden-xs', 'title': 'label.timeBudget'|trans},
|
||||
'budget': {'class': 'hidden-xs', 'title': 'label.budget'|trans},
|
||||
'duration': {'class': 'text-center hw-min', 'title': 'stats.durationMonth'|trans, 'columnClass': 'w-min'},
|
||||
'rate': {'class': 'text-center hw-min', 'title': 'stats.amountMonth'|trans, 'columnClass': 'w-min'},
|
||||
'billable': {'class': 'text-center hw-min', 'columnClass': 'w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
} %}
|
||||
{% set tableName = 'project_daterange_reporting' %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block report %}
|
||||
|
||||
{% set hasData = entries|length > 0 %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "project/actions.html.twig" as projectActions %}
|
||||
{% block box_body_class %}{{ tableName }}-box {% if hasData %}no-padding{% endif %}{% endblock %}
|
||||
{% block box_before %}
|
||||
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': 'project-daterange-form'}}) }}
|
||||
{% endblock %}
|
||||
{% block box_after %}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
|
||||
{% endblock %}
|
||||
{% block box_title %}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if not hasData %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, null, {'bordered': true, 'boxClass': ''}) }}
|
||||
|
||||
{% for id, mapping in entries|sort((a, b) => a.customer.name <=> b.customer.name) %}
|
||||
<tr class="summary">
|
||||
<td colspan="{{ columns|length }}">{{ widgets.label_customer(mapping.customer) }}</td>
|
||||
</tr>
|
||||
{% for entry in mapping.projects|sort((a, b) => a.entity.name <=> b.entity.name) %}
|
||||
{% set project = entry.entity %}
|
||||
{% set currency = project.customer.currency %}
|
||||
{% if is_granted('budget', project) %}
|
||||
<tr {{ widgets.project_row_attr(project, queryEnd) }}>
|
||||
{% for name, column_config in columns %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, name) }}">
|
||||
{% if name == 'name' %}
|
||||
{{ widgets.label_project(project) }}
|
||||
{% elseif name == 'duration' %}
|
||||
{{ entry.statistic.duration|duration }}
|
||||
{% elseif name == 'rate' %}
|
||||
{{ entry.statistic.rate|money(currency) }}
|
||||
{% elseif name == 'billable' %}
|
||||
{{ widgets.percent(entry.statistic.rate, entry.statistic.rateBillable) }}
|
||||
{% elseif name == 'timeBudget' %}
|
||||
{{ progress.progressbar_timebudget(entry) }}
|
||||
{% elseif name == 'budget' %}
|
||||
{{ progress.progressbar_budget(entry, currency) }}
|
||||
{% elseif name == 'actions' %}
|
||||
{{ projectActions.project(project, 'custom') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{{ tables.data_table_footer(entries) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
jQuery('#project-daterange-form').on('change', function(ev) {
|
||||
jQuery(this).submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -57,7 +57,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block report %}
|
||||
{% set hasData = project_view is not null %}
|
||||
{% set hasData = project is not null and project_view is not null %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
@@ -79,8 +79,8 @@
|
||||
{% endif %}
|
||||
|
||||
{% if hasData %}
|
||||
{{ _self.project_details(project_view, project_details, view_revenue_tab, view_revenue_user, see_users) }}
|
||||
{% set currency = project_view.project.customer.currency %}
|
||||
{{ _self.project_details(project, project_view, project_details, view_revenue_tab, view_revenue_user, see_users) }}
|
||||
{% set currency = project.customer.currency %}
|
||||
|
||||
{%- for yearStat in project_details.years|reverse %}
|
||||
{% set year = yearStat.year %}
|
||||
@@ -241,16 +241,16 @@
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
entry = ProjectViewModel
|
||||
project = Project
|
||||
project_view = ProjectViewModel
|
||||
project_details = ProjectDetailsModel
|
||||
#}
|
||||
{% macro project_details(entry, project_details, view_revenue_tab, view_revenue_user, see_users) %}
|
||||
{% macro project_details(project, project_view, project_details, view_revenue_tab, view_revenue_user, see_users) %}
|
||||
{% set activities = project_details.activities %}
|
||||
{% set years = project_details.years %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/charts.html.twig" as charts %}
|
||||
{% set project = entry.project %}
|
||||
{% set currency = project.customer.currency %}
|
||||
{% set chartPrefix = 'project' ~ project.id %}
|
||||
|
||||
@@ -265,14 +265,18 @@
|
||||
|
||||
<div class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs pull-right">
|
||||
{% if see_users %}
|
||||
{% if see_users and project_details.userStats|length > 0 %}
|
||||
<li><a data-chart="{{ chartPrefix }}User" href="#user-chart" data-toggle="tab">{{ 'label.user'|trans }}</a></li>
|
||||
{% endif %}
|
||||
{% if activities is not empty %}
|
||||
<li><a data-chart="{{ chartPrefix }}Activity" href="#activity-chart" data-toggle="tab">{{ 'label.activity'|trans }}</a></li>
|
||||
{% if view_revenue_tab %}
|
||||
{% endif %}
|
||||
{% if view_revenue_tab and project_view.rateTotal > 0 %}
|
||||
<li><a data-chart="{{ chartPrefix }}Rate" href="#revenue-chart" data-toggle="tab">{{ 'stats.revenue'|trans }}</a></li>
|
||||
{% endif %}
|
||||
{% if project_view.durationTotal > 0 %}
|
||||
<li><a data-chart="{{ chartPrefix }}Duration" href="#time-chart" data-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li>
|
||||
{% endif %}
|
||||
<li class="active"><a href="#details-chart" data-toggle="tab">{{ 'report_project_details'|trans({}, 'reporting') }}</a></li>
|
||||
<li class="pull-left header hidden-xs">
|
||||
{{ widgets.label_project(project, {'inherit': false}) }}
|
||||
@@ -283,26 +287,26 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<table class="table table-hover dataTable">
|
||||
<tr {{ widgets.customer_row_attr(entry.project.customer) }}>
|
||||
<tr {{ widgets.customer_row_attr(project.customer) }}>
|
||||
<th class="w-min">
|
||||
{{ 'label.customer'|trans }}
|
||||
</th>
|
||||
<td>
|
||||
{{ widgets.label_customer(entry.project.customer) }}
|
||||
{{ widgets.label_customer(project.customer) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-min">
|
||||
{{ 'stats.durationTotal'|trans }}
|
||||
</th>
|
||||
<td>{{ entry.durationTotal|duration }}</td>
|
||||
<td>{{ project_view.durationTotal|duration }}</td>
|
||||
</tr>
|
||||
{% if view_revenue_tab %}
|
||||
<tr>
|
||||
<th class="w-min">
|
||||
{{ 'stats.amountTotal'|trans }}
|
||||
</th>
|
||||
<td>{{ entry.rateTotal|money(currency) }}</td>
|
||||
<td>{{ project_view.rateTotal|money(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
@@ -314,8 +318,8 @@
|
||||
{{ 'label.last_record'|trans }}
|
||||
</th>
|
||||
<td>
|
||||
{% if entry.lastRecord is not null %}
|
||||
{{ entry.lastRecord|date_short }}
|
||||
{% if project_view.lastRecord is not null %}
|
||||
{{ project_view.lastRecord|date_short }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
@@ -328,7 +332,7 @@
|
||||
</th>
|
||||
<td>
|
||||
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
{{ project_view.notExportedDuration|duration }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -340,7 +344,7 @@
|
||||
</th>
|
||||
<td>
|
||||
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
{{ project_view.notBilledRate|money(currency) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -349,62 +353,58 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if view_revenue_tab and (project.timeBudget > 0 or project.budget > 0) %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table table-hover dataTable">
|
||||
{% if project.timeBudget > 0 %}
|
||||
<tr>
|
||||
<th class="w-min">
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
</th>
|
||||
<td>
|
||||
{% set timeBudgetLeft = project.timeBudget - entry.billableDuration %}
|
||||
{% set options = {
|
||||
'max': project.timeBudget|default(0),
|
||||
'current': entry.billableDuration|default(0),
|
||||
'open': timeBudgetLeft,
|
||||
'format': 'duration'
|
||||
} %}
|
||||
{{ progress.progressbar_full(options) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.budget > 0 %}
|
||||
<tr>
|
||||
<th class="w-min">
|
||||
{{ 'label.budget'|trans }}
|
||||
</th>
|
||||
<td>
|
||||
{% set budgetLeft = project.budget - entry.billableRate %}
|
||||
{% set options = {
|
||||
'max': project.budget|default(0),
|
||||
'current': entry.billableRate|default(0),
|
||||
'open': budgetLeft,
|
||||
'format': 'money',
|
||||
'currency': currency
|
||||
} %}
|
||||
{{ progress.progressbar_full(options) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% set budgetStats = project_details.budgetStatisticModel %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table table-hover dataTable">
|
||||
{% if project.timeBudget > 0 %}
|
||||
<tr>
|
||||
<th class="w-min">
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
{% if budgetStats.isMonthlyBudget() %}
|
||||
({{ 'label.budgetType_month'|trans }})
|
||||
{% endif %}
|
||||
</th>
|
||||
<td>
|
||||
{{ progress.progressbar_timebudget(budgetStats) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.budget > 0 %}
|
||||
<tr>
|
||||
<th class="w-min">
|
||||
{{ 'label.budget'|trans }}
|
||||
{% if budgetStats.isMonthlyBudget() %}
|
||||
({{ 'label.budgetType_month'|trans }})
|
||||
{% endif %}
|
||||
</th>
|
||||
<td>
|
||||
{{ progress.progressbar_budget(budgetStats, project.customer.currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% if view_revenue_tab %}
|
||||
{% if view_revenue_tab and project_view.rateTotal > 0 %}
|
||||
<div class="chart tab-pane" id="revenue-chart">
|
||||
{{ charts.bar_chart(chartPrefix ~ 'Rate', labels, [rates], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Rate'}) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if project_view.durationTotal > 0 %}
|
||||
<div class="chart tab-pane" id="time-chart">
|
||||
{{ charts.bar_chart(chartPrefix ~ 'Duration', labels, [durations], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Duration'}) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if activities is not empty %}
|
||||
<div class="chart tab-pane" id="activity-chart">
|
||||
{{ _self.activity_tab(activities, entry.durationTotal, project.customer.currency, chartPrefix, view_revenue_tab) }}
|
||||
{{ _self.activity_tab(activities, project_view.durationTotal, project.customer.currency, chartPrefix, view_revenue_tab) }}
|
||||
</div>
|
||||
{% if see_users %}
|
||||
{% endif %}
|
||||
{% if see_users and project_details.userStats|length > 0 %}
|
||||
<div class="chart tab-pane" id="user-chart">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-9 col-md-8 col-lg-6">
|
||||
@@ -420,8 +420,8 @@
|
||||
{% set totalDuration = totalDuration + userStat.duration %}
|
||||
{% set datasets = datasets|merge([{'name': user.displayName, 'duration': userStat.duration|duration, 'value': userStat.duration, 'color': color, 'rate': userStat.rate|money(currency)}]) %}
|
||||
{% set percentage = 0 %}
|
||||
{% if entry.durationTotal > 0 and userStat.duration > 0 %}
|
||||
{% set percentage = (100 / (entry.durationTotal / userStat.duration)) %}
|
||||
{% if project_view.durationTotal > 0 and userStat.duration > 0 %}
|
||||
{% set percentage = (100 / (project_view.durationTotal / userStat.duration)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -3,30 +3,30 @@
|
||||
|
||||
{% block report_title %}{{ (title|default('report_project_view'))|trans({}, 'reporting') }}{% endblock %}
|
||||
|
||||
{% set showDurations = showDurations is defined and showDurations is same as (true) %}
|
||||
{% set columns = {
|
||||
{% set availableColumns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'lastRecord': {'class': 'hidden text-center hw-min', 'title': 'label.last_record'|trans},
|
||||
} %}
|
||||
{% if showDurations %}
|
||||
{% set columns = columns|merge({
|
||||
'today': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationToday'|trans},
|
||||
'week': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationWeek'|trans},
|
||||
'month': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationMonth'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
'durationTotal': {'class': 'text-center hw-min', 'title': 'stats.durationTotal'|trans},
|
||||
'timeBudget': {'class': 'hidden-xs', 'title': 'label.timeBudget'|trans},
|
||||
'budget': {'class': 'hidden-xs', 'title': 'label.budget'|trans},
|
||||
'stateDuration': {'class': 'hidden-sm hidden-xs text-center hw-min', 'title': 'label.not_exported'|trans},
|
||||
'stateMoney': {'class': 'hidden-sm hidden-xs text-center hw-min', 'title': 'label.not_invoiced'|trans},
|
||||
'lastRecord': {'class': 'text-center hw-min', 'title': 'label.last_record'|trans, 'columnClass': 'w-min'},
|
||||
'today': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationToday'|trans},
|
||||
'week': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationWeek'|trans},
|
||||
'month': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationMonth'|trans},
|
||||
'durationTotal': {'class': 'text-center hw-min', 'title': 'stats.durationTotal'|trans, 'columnClass': 'w-min'},
|
||||
'exported': {'class': 'hidden-sm hidden-xs hidden text-center hw-min', 'title': 'label.not_exported'|trans, 'columnClass': 'w-min'},
|
||||
'invoiced': {'class': 'hidden-sm hidden-xs hidden text-center hw-min', 'title': 'label.not_invoiced'|trans, 'columnClass': 'w-min'},
|
||||
'projectStart': {'class': 'hidden-md hidden-sm hidden-xs hidden text-center w-min', 'title': 'label.project_start'|trans},
|
||||
'projectEnd': {'class': 'hidden-md hidden-sm hidden-xs hidden text-center w-min', 'title': 'label.project_end'|trans},
|
||||
'comment': {'class': 'hidden-md hidden-sm hidden-xs hidden', 'title': 'label.comment'|trans},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
}) %}
|
||||
} %}
|
||||
{% set tableName = tableName|default('project_view_reporting') %}
|
||||
{% set skipColumns = skipColumns is defined ? skipColumns : {} %}
|
||||
{% set columns = {} %}
|
||||
{% for name, config in availableColumns %}
|
||||
{% if name not in skipColumns %}
|
||||
{% set columns = columns|merge({(name): config}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
@@ -66,10 +66,14 @@
|
||||
</tr>
|
||||
{% for entry in mapping.projects|sort((a, b) => a.project.name <=> b.project.name) %}
|
||||
{% set project = entry.project %}
|
||||
{% set budgetStats = entry.getBudgetStatisticModel() %}
|
||||
{% set currency = project.customer.currency %}
|
||||
{% if is_granted('budget', project) %}
|
||||
<tr{% if is_granted('details', project) %} class="alternative-link open-edit" data-href="{{ path('report_project_details', {'project': project.id}) }}"{% endif %}>
|
||||
<tr {{ widgets.project_row_attr(project, now) }}>
|
||||
{% for name, column_config in columns %}
|
||||
{% if name == 'name' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_project(project) }}</td>
|
||||
{% elseif name == 'lastRecord' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'lastRecord') }}">
|
||||
{% if entry.lastRecord is not null %}
|
||||
{{ entry.lastRecord|date_short }}
|
||||
@@ -77,38 +81,28 @@
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if showDurations %}
|
||||
{% elseif name == 'today' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'today') }}">{{ entry.durationDay|duration }}</td>
|
||||
{% elseif name == 'week' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'week') }}">{{ entry.durationWeek|duration }}</td>
|
||||
{% elseif name == 'month' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'month') }}">{{ entry.durationMonth|duration }}</td>
|
||||
{% endif %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'durationTotal') }}">{{ entry.durationTotal|duration }}</td>
|
||||
{% elseif name == 'durationTotal' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'durationTotal') }} w-min">{{ entry.durationTotal|duration }}</td>
|
||||
{% elseif name == 'timeBudget' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
|
||||
{% if project.timeBudget > 0 %}
|
||||
{% set timeBudgetLeft = project.timeBudget - entry.billableDuration %}
|
||||
{% set options = {
|
||||
'max': project.timeBudget|default(0),
|
||||
'current': entry.billableDuration|default(0),
|
||||
'open': timeBudgetLeft,
|
||||
'format': 'duration'
|
||||
} %}
|
||||
{{ progress.progressbar_full(options) }}
|
||||
{% if budgetStats.hasTimeBudget() %}
|
||||
{{ progress.progressbar_timebudget(budgetStats) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% elseif name == 'budget' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'budget') }}">
|
||||
{% if project.budget > 0 %}
|
||||
{% set budgetLeft = project.budget - entry.billableRate %}
|
||||
{% set options = {
|
||||
'max': project.budget|default(0),
|
||||
'current': entry.billableRate|default(0),
|
||||
'open': budgetLeft,
|
||||
'format': 'money',
|
||||
'currency': currency
|
||||
} %}
|
||||
{{ progress.progressbar_full(options) }}
|
||||
{% if project.hasBudget() %}
|
||||
{{ progress.progressbar_budget(budgetStats, project.customer.currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'stateDuration') }}">
|
||||
{% elseif name == 'exported' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}">
|
||||
{% if is_granted('create_export') %}
|
||||
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
@@ -117,7 +111,8 @@
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'stateMoney') }}">
|
||||
{% elseif name == 'invoiced' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'invoiced') }}">
|
||||
{% if is_granted('view_invoice') %}
|
||||
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
@@ -126,12 +121,18 @@
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% elseif name == 'projectStart' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'projectStart') }}">{% if project.start is not null %}{{ project.start|date_short }}{% endif %}</td>
|
||||
{% elseif name == 'projectEnd' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'projectEnd') }}">{% if project.end is not null %}{{ project.end|date_short }}{% endif %}</td>
|
||||
{% elseif name == 'comment' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ project.comment }}</td>
|
||||
{% elseif name == 'actions' %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{{ projectActions.project(project, 'custom') }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -147,8 +148,8 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
$('#project-view-form').on('change', function(ev) {
|
||||
$(this).submit();
|
||||
jQuery('#project-view-form').on('change', function(ev) {
|
||||
jQuery(this).submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
|
||||
{% block report %}
|
||||
|
||||
{% set hasData = false %}
|
||||
{% for day in days %}
|
||||
{% if day.details is not empty %}
|
||||
{% set hasData = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_before %}
|
||||
@@ -27,67 +20,72 @@
|
||||
{% endif %}
|
||||
{{ form_widget(form.date) }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}{{ box_id }} table-responsive{% if hasData %} no-padding{% endif %}{% endblock %}
|
||||
{% block box_body_class %}{{ box_id }} table-responsive no-padding{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if not hasData %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% set totals = {'totals': 0} %}
|
||||
{% set columns = 2 %}
|
||||
<table class="table table-bordered table-hover dataTable">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for day in days %}
|
||||
<th class="text-center text-nowrap{% if day.day is weekend %} weekend{% endif %}">
|
||||
{{ day.day|day_name(true) }}<br>
|
||||
{{ day.day|format_date('short') }}
|
||||
</th>
|
||||
{% for day in days.dateTimes %}
|
||||
<th class="text-center text-nowrap{% if day is weekend %} weekend{% endif %}">
|
||||
{{ day|day_name(true) }}<br>
|
||||
{{ day|format_date('short') }}
|
||||
</th>
|
||||
{% set columns = columns + 1 %}
|
||||
{% set totals = totals|merge({(day|report_date): 0}) %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for project in rows %}
|
||||
{% set oldCustomer = null %}
|
||||
{% for pid, project in rows|sort((a,b) => a.project.customer.id <=> b.project.customer.id) %}
|
||||
{% if oldCustomer is null or oldCustomer != project.project.customer.id %}
|
||||
{% set oldCustomer = project.project.customer.id %}
|
||||
<tr class="summary">
|
||||
<td colspan="{{ columns }}">{{ widgets.label_customer(project.project.customer) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% set totals = totals|merge({'totals': (totals['totals'] + project.duration)}) %}
|
||||
<tr class="project">
|
||||
<td class="text-nowrap">
|
||||
<strong>{{ widgets.label_project(project.project) }}</strong>
|
||||
</td>
|
||||
<th class="text-nowrap text-center total">{{ project.duration|duration }}</th>
|
||||
{% for day in project.days %}
|
||||
{% for day in project.days.days %}
|
||||
<td class="text-nowrap text-center day-total{% if day.date is weekend %} weekend{% endif %}">
|
||||
{% if day.duration > 0 %}
|
||||
{% set totals = totals|merge({(day.date|report_date): (totals[day.date|report_date] + day.duration)}) %}
|
||||
<strong>{{ day.duration|duration }}</strong>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for activity in project.activities %}
|
||||
<tr class="activity">
|
||||
<td class="text-nowrap">
|
||||
{{ widgets.label_activity(activity.activity) }}
|
||||
</td>
|
||||
<th class="text-nowrap text-center total">{{ activity.duration|duration }}</th>
|
||||
{% for day in activity.days %}
|
||||
<td class="text-nowrap text-center day-total{% if day.date is weekend %} weekend{% endif %}">
|
||||
{% if day.duration > 0 %}
|
||||
{{ day.duration|duration }}
|
||||
{% endif %}
|
||||
<tr class="activity">
|
||||
<td class="text-nowrap">
|
||||
{{ widgets.label_activity(activity.activity) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<th class="text-nowrap text-center total">{{ activity.duration|duration }}</th>
|
||||
{% for day in activity.days.days %}
|
||||
<td class="text-nowrap text-center day-total{% if day.date is weekend %} weekend{% endif %}">
|
||||
{% if day.duration > 0 %}
|
||||
{{ day.duration|duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% set total = 0 %}
|
||||
<tr class="summary">
|
||||
{% for day in days %}
|
||||
{% set total = total + day.totalDuration %}
|
||||
{% endfor %}
|
||||
<th></th>
|
||||
<th class="text-nowrap text-center total">{{ total|duration }}</th>
|
||||
{% for day in days %}
|
||||
<th class="text-nowrap text-center day-total{% if day.day is weekend %} weekend{% endif %}">
|
||||
{{ day.totalDuration|duration }}
|
||||
<th class="text-nowrap text-center total">{{ totals['totals']|duration }}</th>
|
||||
{% for day in days.dateTimes %}
|
||||
<th class="text-nowrap text-center day-total{% if day is weekend %} weekend{% endif %}">
|
||||
{{ totals[day|report_date]|duration }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
@@ -98,12 +96,12 @@
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
{% if form.user is defined %}
|
||||
$('#{{ form.user.vars.id }}').on('change', function(ev) {
|
||||
$(this).closest('form').submit();
|
||||
jQuery('#{{ form.user.vars.id }}').on('change', function(ev) {
|
||||
jQuery(this).closest('form').submit();
|
||||
});
|
||||
{% endif %}
|
||||
$('#{{ form.date.vars.id }}').on('change', function(ev) {
|
||||
$(this).closest('form').submit();
|
||||
jQuery('#{{ form.date.vars.id }}').on('change', function(ev) {
|
||||
jQuery(this).closest('form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -15,56 +15,63 @@
|
||||
{% block box_title %}
|
||||
{{ form_widget(form.date) }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}{{ box_id }} table-responsive no-padding{% endblock %}
|
||||
{% block box_body_class %}{{ box_id }} table-responsive {% if hasData %}no-padding{% endif %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set absoluteTotals = 0 %}
|
||||
<table class="table table-bordered table-hover dataTable">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for day in days %}
|
||||
<th class="text-center text-nowrap{% if day is weekend %} weekend{% endif %}">
|
||||
{{ day|day_name(true) }}<br>
|
||||
{{ day|format_date('short') }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for userDay in rows %}
|
||||
{% set usersTotalDuration = 0 %}
|
||||
<tr class="user">
|
||||
<td class="text-nowrap">
|
||||
{{ widgets.label_dot(userDay.user.displayName, userDay.user.color) }}
|
||||
</td>
|
||||
{% for day in userDay.days %}
|
||||
{% if day.totalDuration > 0 %}
|
||||
{% set usersTotalDuration = usersTotalDuration + day.totalDuration %}
|
||||
{% set absoluteTotals = absoluteTotals + day.totalDuration %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<th class="text-nowrap text-center total">
|
||||
{{ usersTotalDuration|duration }}
|
||||
</th>
|
||||
{% for day in userDay.days %}
|
||||
<td class="text-nowrap text-center day-total{% if day.day is weekend %} weekend{% endif %}">
|
||||
{% if day.totalDuration > 0 %}
|
||||
{{ day.totalDuration|duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if not hasData %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% set absoluteTotals = 0 %}
|
||||
{% set totals = {} %}
|
||||
<table class="table table-bordered table-hover dataTable">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for day in stats.0.getDateTimes() %}
|
||||
<th class="text-center text-nowrap{% if day is weekend %} weekend{% endif %}">
|
||||
{{ day|day_name(true) }}<br>
|
||||
{{ day|format_date('short') }}
|
||||
</th>
|
||||
{% set totals = totals|merge({(day|report_date): 0}) %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th class="text-center text-nowrap">
|
||||
{{ absoluteTotals|duration }}
|
||||
</th>
|
||||
{% for id, duration in totals %}
|
||||
<th class="text-center text-nowrap">
|
||||
{{ duration|duration }}
|
||||
</th>
|
||||
{% for userDay in stats %}
|
||||
{% set usersTotalDuration = 0 %}
|
||||
<tr class="user">
|
||||
<td class="text-nowrap">
|
||||
{{ widgets.label_dot(userDay.user.displayName, userDay.user.color) }}
|
||||
</td>
|
||||
{% for day in userDay.days %}
|
||||
{% if day.totalDuration > 0 %}
|
||||
{% set usersTotalDuration = usersTotalDuration + day.totalDuration %}
|
||||
{% set absoluteTotals = absoluteTotals + day.totalDuration %}
|
||||
{% endif %}
|
||||
{% set totals = totals|merge({(day.date|report_date): (totals[day.date|report_date] + day.totalDuration)}) %}
|
||||
{% endfor %}
|
||||
<th class="text-nowrap text-center total">
|
||||
<a href="{{ path(subReportRoute, {'date': subReportDate|report_date, 'user': userDay.user.id}) }}">{{ usersTotalDuration|duration }}</a>
|
||||
</th>
|
||||
{% for day in userDay.days %}
|
||||
<td class="text-nowrap text-center day-total{% if day.date is weekend %} weekend{% endif %}">
|
||||
{% if day.totalDuration > 0 %}
|
||||
{{ day.totalDuration|duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</table>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th class="text-center text-nowrap">
|
||||
{{ absoluteTotals|duration }}
|
||||
</th>
|
||||
{% for id, duration in totals %}
|
||||
<th class="text-center text-nowrap">
|
||||
{{ duration|duration }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
@@ -74,8 +81,8 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
$('#{{ form.date.vars.id }}').on('change', function(ev) {
|
||||
$(this).closest('form').submit();
|
||||
jQuery('#{{ form.date.vars.id }}').on('change', function(ev) {
|
||||
jQuery(this).closest('form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_before %}
|
||||
{{ form_start(form, {'attr': {'class': 'form-inline'}}) }}
|
||||
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting'}}) }}
|
||||
{% endblock %}
|
||||
{% block box_after %}
|
||||
{{ form_end(form) }}
|
||||
@@ -15,60 +15,63 @@
|
||||
{% block box_title %}
|
||||
{{ form_widget(form.date) }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}{{ box_id }} table-responsive no-padding{% endblock %}
|
||||
{% block box_body_class %}{{ box_id }} table-responsive {% if hasData %}no-padding{% endif %}{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-bordered table-hover dataTable">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for id, month in months %}
|
||||
<th class="text-center text-nowrap">
|
||||
<a href="{{ path('report_monthly_users', {'date': month|date_short}) }}">
|
||||
{{ month|month_name }}<br>
|
||||
{{ month|date_format('Y') }}
|
||||
</a>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for userYear in rows %}
|
||||
{% set usersTotalDuration = 0 %}
|
||||
<tr class="user">
|
||||
<td class="text-nowrap">
|
||||
{{ widgets.label_dot(userYear.user.displayName, userYear.user.color) }}
|
||||
</td>
|
||||
{% for yid, year in userYear.years %}
|
||||
{% for mid, month in year.months %}
|
||||
{% if month.totalDuration > 0 %}
|
||||
{% set usersTotalDuration = usersTotalDuration + month.totalDuration %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not hasData %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% set totals = {} %}
|
||||
<table class="table table-bordered table-hover dataTable">
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for month in stats.0.getDateTimes() %}
|
||||
<th class="text-center text-nowrap">
|
||||
<a href="{{ path('report_monthly_users', {'date': month|report_date}) }}">
|
||||
{{ month|month_name }}<br>
|
||||
{{ month|date_format('Y') }}
|
||||
</a>
|
||||
</th>
|
||||
{% set totals = totals|merge({(month|report_date): 0}) %}
|
||||
{% endfor %}
|
||||
<th class="text-nowrap text-center total">
|
||||
{{ usersTotalDuration|duration }}
|
||||
</th>
|
||||
{% for yid, year in userYear.years %}
|
||||
{% for mid, month in year.months %}
|
||||
</tr>
|
||||
{% for userYear in stats|filter(row => row.user is not null) %}
|
||||
{% set usersTotalDuration = 0 %}
|
||||
<tr class="user">
|
||||
<td class="text-nowrap">
|
||||
{{ widgets.label_dot(userYear.user.displayName, userYear.user.color) }}
|
||||
</td>
|
||||
{% for mid, month in userYear.months %}
|
||||
{% if month.totalDuration > 0 %}
|
||||
{% set usersTotalDuration = usersTotalDuration + month.totalDuration %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<th class="text-nowrap text-center total">
|
||||
{{ usersTotalDuration|duration }}
|
||||
</th>
|
||||
{% for mid, month in userYear.getMonths() %}
|
||||
<td class="text-nowrap text-center day-total">
|
||||
{% if month.totalDuration > 0 %}
|
||||
<a href="{{ path('report_user_month', {'date': (year.year ~'-'~month.month~'-01')|date_short, 'user': userYear.user.id}) }}" data-toggle="tooltip" title="{{ 'label.billable'|trans }}: {{ month.billableDuration|duration }}">
|
||||
<a href="{{ path('report_user_month', {'date': create_date(month.date.format('Y') ~'-'~month.date.format('m')~'-01')|report_date, 'user': userYear.user.id}) }}" data-toggle="tooltip" title="{{ 'label.billable'|trans }}: {{ month.billableDuration|duration }}">
|
||||
{{ month.totalDuration|duration }}
|
||||
</a>
|
||||
{% set totals = totals|merge({(month.date|report_date): (totals[month.date|report_date] + month.totalDuration)}) %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for id, total in totals %}
|
||||
<th class="text-center text-nowrap">
|
||||
{{ total|duration }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
{% for id, duration in totals %}
|
||||
<th class="text-center text-nowrap">
|
||||
{{ duration|duration }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
@@ -78,8 +81,8 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
$('#{{ form.date.vars.id }}').on('change', function(ev) {
|
||||
$(this).closest('form').submit();
|
||||
jQuery('#{{ form.date.vars.id }}').on('change', function(ev) {
|
||||
jQuery(this).closest('form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -276,12 +276,20 @@
|
||||
duration = parseFloat(duration) * 3600;
|
||||
momentDuration = moment.duration('PT' + duration + 'S');
|
||||
} else if (duration.indexOf('H') !== -1 || duration.indexOf('M') !== -1 || duration.indexOf('S') !== -1) {
|
||||
{# D for days does not work, because 'PT1H' but with days 'P1D' is used #}
|
||||
momentDuration = moment.duration('PT' + duration);
|
||||
} else {
|
||||
let c = parseInt(duration);
|
||||
let d = parseInt(duration).toFixed();
|
||||
if (!isNaN(c) && duration === d) {
|
||||
duration = c * 3600;
|
||||
momentDuration = moment.duration('PT' + duration + 'S');
|
||||
}
|
||||
}
|
||||
|
||||
return momentDuration;
|
||||
}
|
||||
|
||||
|
||||
function applyDateToField(field, momentObj, format)
|
||||
{
|
||||
field.value = momentObj.format(format);
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
{% set inUse = (stats.recordsTotal > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%user%': widgets.username(user),
|
||||
'%records%': stats.recordsTotal,
|
||||
'%duration%': stats.durationTotal|duration
|
||||
} %}
|
||||
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', {
|
||||
'message': ("admin_user.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'message': ("delete_warning.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'form': form,
|
||||
'used': inUse,
|
||||
'back': path('admin_user')
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block profile_navbar %}
|
||||
{{ _self.about_me(user, stats) }}
|
||||
{{ _self.about_me(user, stats, firstTimesheet) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block profile_intro %}{% endblock %}
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
{{ _self.profile_box_horizontal(user, stats) }}
|
||||
|
||||
{% if years is empty %}
|
||||
{% if workMonths is empty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% endif %}
|
||||
|
||||
@@ -33,17 +33,17 @@
|
||||
|
||||
{{ charts.bar_javascript({'footer': 'footer', 'label': 'tooltip', 'onclick': {'url': monthRoute, 'replacer': {'__MONTH__': 'month'}}}) }}
|
||||
|
||||
{% for yearStat in years %}
|
||||
{% for year in workMonths.years|reverse %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/charts.html.twig" as charts %}
|
||||
{% block box_title %}{{ yearStat.year }}{% endblock %}
|
||||
{% block box_title %}{{ year }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set dataset = [] %}
|
||||
{% for month in yearStat.months %}
|
||||
{% set monthDate = create_date(yearStat.year ~ '-' ~ month.month ~ '-01') %}
|
||||
{% set dataset = dataset|merge([{'value': month.duration|chart_duration, 'tooltip': month.duration|duration, 'footer': ('label.billable'|trans ~ ': ' ~ month.billableDuration|duration), 'month': monthDate|date_short}]) %}
|
||||
{% for month in workMonths.year(year) %}
|
||||
{% set monthDate = create_date(year ~ '-' ~ month.date.format('m') ~ '-01') %}
|
||||
{% set dataset = dataset|merge([{'value': month.duration|chart_duration, 'tooltip': month.duration|duration, 'footer': ('label.billable'|trans ~ ': ' ~ month.billableDuration|duration), 'month': monthDate|report_date}]) %}
|
||||
{% endfor %}
|
||||
{{ charts.bar_chart('userProfileChart'~yearStat.year, month_names(), [dataset], {}) }}
|
||||
{{ charts.bar_chart('userProfileChart'~year, month_names(), [dataset], {}) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endfor %}
|
||||
@@ -69,7 +69,7 @@
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% macro about_me(user, stats) %}
|
||||
{% macro about_me(user, stats, firstTimesheet) %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
@@ -81,13 +81,20 @@
|
||||
<strong>{{ 'label.username'|trans }}</strong><br>
|
||||
{{ user.username }}
|
||||
</p>
|
||||
{% if user.accountNumber is not empty %}
|
||||
<p>
|
||||
<strong>{{ 'label.account_number'|trans }}</strong><br>
|
||||
{{ user.accountNumber }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<strong>{{ 'profile.first_entry'|trans }}</strong><br>
|
||||
{{ stats.firstEntry|date_short }}
|
||||
{% if firstTimesheet is not null %}
|
||||
{{ firstTimesheet|date_short }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ 'profile.registration_date'|trans }}</strong><br>
|
||||
|
||||
@@ -16,29 +16,19 @@
|
||||
{% block box_body %}
|
||||
<table class="table table-hover dataTable" role="grid">
|
||||
<tbody>
|
||||
{% for row in projectStats|sort((a, b) => a.project.name <=> b.project.name) %}
|
||||
{% set stats = row.stats %}
|
||||
{% set project = row.project %}
|
||||
{% for row in projectStats|sort((a, b) => a.entity.name <=> b.entity.name) %}
|
||||
{% set project = row.entity %}
|
||||
<tr{% if is_granted('details', project) %} class="alternative-link open-edit" data-href="{{ path('project_details', {'id': project.id}) }}"{% endif %}>
|
||||
<td>
|
||||
{{ widgets.label_project(project) }}
|
||||
<br>
|
||||
<small>{{ widgets.label_customer(project.customer) }}</small>
|
||||
</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">
|
||||
{% for team in project.teams %}
|
||||
{% if app.user.isInTeam(team) %}
|
||||
{{ team.name }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td style="width:50%">
|
||||
{% if project.timeBudget is not empty and project.timeBudget > 0 %}
|
||||
{% set budgetLeft = project.timeBudget - stats.recordDuration %}
|
||||
{{ progress.progressbar_small(project.timeBudget, stats.durationBillable, budgetLeft, budgetLeft|duration) }}
|
||||
{% elseif project.budget is not empty and project.budget > 0 %}
|
||||
{% set budgetLeft = project.budget - stats.recordRate %}
|
||||
{{ progress.progressbar_small(project.budget, stats.rateBillable, budgetLeft, budgetLeft|money(project.customer.currency)) }}
|
||||
{% if row.hasTimeBudget() %}
|
||||
{{ progress.progressbar_timebudget(row) }}
|
||||
{% elseif row.hasBudget() %}
|
||||
{{ progress.progressbar_budget(row, row.entity.customer.currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user