monthly budget, monthly report, unified report calculation (#2684)
This commit is contained in:
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>
|
||||
|
||||
Reference in New Issue
Block a user