monthly budget, monthly report, unified report calculation (#2684)

This commit is contained in:
Kevin Papst
2021-08-06 18:38:41 +02:00
committed by GitHub
parent 21f785638c
commit cefd747e91
196 changed files with 5031 additions and 2167 deletions

View 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 %}

View File

@@ -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 %}
&ndash;
{% 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>

View File

@@ -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 @@
&ndash;
{% 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>

View File

@@ -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>&nbsp;</th>
<th>&nbsp;</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>

View File

@@ -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>&nbsp;</th>
<th>&nbsp;</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>&nbsp;</th>
<th>&nbsp;</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>&nbsp;</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>&nbsp;</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>

View File

@@ -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>&nbsp;</th>
<th>&nbsp;</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>&nbsp;</th>
<th>&nbsp;</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>&nbsp;</th>
<th>&nbsp;</th>
{% for id, total in totals %}
<th class="text-center text-nowrap">
{{ total|duration }}
</th>
{% endfor %}
</tr>
{% endfor %}
<tr>
<th>&nbsp;</th>
<th>&nbsp;</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>