Release 2.23.0 (#5075)

This commit is contained in:
Kevin Papst
2024-10-03 10:34:20 +02:00
committed by GitHub
parent 40154be8f9
commit fb9a0dc499
142 changed files with 855 additions and 910 deletions

View File

@@ -13,9 +13,5 @@
<spacer size="16"></spacer>
<p><small>{{ 'button_copy_url_to_browser'|trans({'%url%': confirmationUrl}, 'email') }}</small></p>
<spacer size="16"></spacer>
<p><small>{{ 'automated_email_dont_answer'|trans({}, 'email') }}</small></p>
{% endblock %}

View File

@@ -1,29 +1,15 @@
{% apply inky_to_html|inline_css(source('@css/emails.css')) %}
<style type="text/css">
body, html, .body {
background: #f3f3f3 !important;
}
.container.header {
background: #f3f3f3;
}
</style>
<spacer size="32"></spacer>
<container class="header">
<row>
<columns>
<h1 class="text-center">{% block title %}{% endblock %}</h1>
</columns>
</row>
</container>
<h5>{% block title %}{% endblock %}</h5>
<spacer size="32"></spacer>
{% block body %}{% endblock %}
<spacer size="32"></spacer>
<p><small>{{ 'automated_email_dont_answer'|trans({}, 'email') }}</small></p>
<container class="body-border">
<row>
<columns>
<spacer size="32"></spacer>
{% block body %}{% endblock %}
</columns>
</row>
</container>
{% endapply %}

View File

@@ -9,13 +9,5 @@
<spacer size="16"></spacer>
<button class="large primary" href="{{ confirmationUrl }}">{{ 'reset.button'|trans({'%url%': confirmationUrl}, 'email') }}</button>
<spacer size="16"></spacer>
<p><small>{{ 'button_copy_url_to_browser'|trans({'%url%': confirmationUrl}, 'email') }}</small></p>
<spacer size="16"></spacer>
<p><small>{{ 'automated_email_dont_answer'|trans({}, 'email') }}</small></p>
<button class="rounded primary" href="{{ confirmationUrl }}">{{ 'reset.button'|trans({'%url%': confirmationUrl}, 'email') }}</button>
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% set timeBudgetVisible = is_granted('time', entity) %}
{% set durationTrans = entity.isMonthlyBudget() ? 'stats.durationMonth' : 'stats.durationTotal' %}
{% set durationTrans = entity.isMonthlyBudget() ? 'stats.durationMonth' : 'stats.workingTime' %}
{% if timeBudgetVisible %}
{% embed '@theme/embeds/card.html.twig' %}
{% import "macros/progressbar.html.twig" as progress %}
@@ -22,54 +22,29 @@
-
{% endif %}
</td>
<td class="text-nowrap text-end w-min">
{% 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-end">{{ stats.duration|duration }}</td>
<td class="text-nowrap text-end">{{ totalPercentReached|number_format(2) }}%</td>
</tr>
{% set percentReached = 0 %}
{% if stats.duration > 0 %}
{% set percentReached = (stats.durationBillable / (stats.duration / 100)) %}
{% endif %}
<tr>
<td>{{ 'billable'|trans }}</td>
<td class="text-nowrap text-end">{{ stats.durationBillable|duration }}</td>
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
</tr>
<tr>
<td>{{ 'not_exported'|trans }}</td>
<td class="text-nowrap text-end">
{% set not_exported = (stats.statisticTotal.durationBillable - stats.statisticTotal.durationBillableExported) %}
{% if export_url is defined and export_url is not null %}
<a href="{{ export_url }}">{{ not_exported|duration }}</a>
{% else %}
{{ not_exported|duration }}
{% endif %}
</td>
<td class="text-nowrap text-end">-</td>
</tr>
{% if entity.timeBudget > 0 %}
<tr>
<th colspan="3" class="text-nowrap text-end">
{% 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>
{% set totalPercentReached = 100 %}
{% if entity.timeBudget > 0 %}
{% set totalPercentReached = (stats.durationBillable / (entity.timeBudget / 100)) %}
{% endif %}
<tr>
<th colspan="2" class="text-nowrap text-end">
{% 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>
@@ -93,7 +68,6 @@
{% block box_body_class %}p-0{% endblock %}
{% block box_attributes %}id="budget_box"{% endblock %}
{% block box_body %}
{% set rateTrans = entity.isMonthlyBudget() ? 'stats.amountMonth' : 'stats.amountTotal' %}
<div class="row">
<div class="col-12 col-md-6 order-1 order-md-0">
<table class="table table-hover dataTable">
@@ -106,80 +80,45 @@
-
{% endif %}
</td>
<td class="text-nowrap text-end w-min">
{% 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-end">{{ stats.rate|money(currency) }}</td>
<td class="text-nowrap text-end">{{ totalPercentReached|number_format(2) }}%</td>
</tr>
{% set percentReached = 0 %}
{% if stats.rate > 0 %}
{% set percentReached = (stats.rateBillable / (stats.rate / 100)) %}
{% endif %}
<tr>
<td>{{ 'billable'|trans }}</td>
<td>{{ 'revenue'|trans }}</td>
<td class="text-nowrap text-end">{{ stats.rateBillable|money(currency) }}</td>
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
</tr>
<tr>
<td>{{ 'not_invoiced'|trans }}</td>
<td class="text-nowrap text-end">
{% set not_exported = (stats.statisticTotal.rateBillable - stats.statisticTotal.rateBillableExported) %}
{% if invoice_url is defined and invoice_url is not null %}
<a href="{{ invoice_url }}">{{ not_exported|money(currency) }}</a>
{% else %}
{{ not_exported|money(currency) }}
{% endif %}
</td>
<td class="text-nowrap text-end">-</td>
</tr>
{% set percentReached = 0 %}
{% if stats.rateBillable > 0 %}
{% set percentReached = (stats.internalRate / (stats.rateBillable / 100)) %}
{% endif %}
<tr>
<td>{{ 'internalRate'|trans }}</td>
<td>{{ 'costs'|trans }}</td>
<td class="text-nowrap text-end">{{ stats.internalRate|money(currency) }}</td>
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
</tr>
{% if not entity.isMonthlyBudget() and timeBudgetVisible and stats.duration > 0 and stats.duration|chart_duration > 0.00 %}
{% set revenueTotal = stats.rateBillable - stats.internalRate %}
{% if stats.internalRate > 0 %}
{% set revenueTotal = stats.rateBillable - stats.internalRate %}
<tr>
<td>{{ 'stats.revenue'|trans }} ({{ 'billable'|trans }} - {{ 'internalRate'|trans }})</td>
<td>{{ 'profit'|trans }} ({{ 'revenue'|trans }} - {{ 'costs'|trans }})</td>
<td class="text-nowrap text-end">{{ revenueTotal|money(currency) }}</td>
<td class="text-nowrap text-end">-</td>
</tr>
{% endif %}
{% set realHourlyRate = stats.rateBillable / stats.duration|chart_duration %}
<tr>
<td>{{ 'hourlyRate'|trans }} ({{ 'billable'|trans }} / {{ durationTrans|trans }})</td>
<td class="text-nowrap text-end">{{ realHourlyRate|money(currency) }}</td>
<td class="text-nowrap text-end">-</td>
</tr>
{% if stats.internalRate > 0 %}
{% set revenueHourlyRate = revenueTotal / stats.duration|chart_duration %}
{% if stats.duration > 0 %}
{% set realHourlyRate = stats.rateBillable / stats.duration|chart_duration %}
<tr>
<td>{{ 'hourlyRate'|trans }} ({{ 'stats.revenue'|trans }} / {{ durationTrans|trans }})</td>
<td class="text-nowrap text-end">{{ revenueHourlyRate|money(currency) }}</td>
<td class="text-nowrap text-end">-</td>
<td>{{ 'hourlyRate'|trans }} ({{ 'revenue'|trans }} / {{ durationTrans|trans }})</td>
<td class="text-nowrap text-end">{{ realHourlyRate|money(currency) }}</td>
</tr>
{% if revenueTotal != 0.0 %}
{% set revenueHourlyRate = revenueTotal / stats.duration|chart_duration %}
<tr>
<td>{{ 'hourlyRate'|trans }} ({{ 'profit'|trans }} / {{ durationTrans|trans }})</td>
<td class="text-nowrap text-end">{{ revenueHourlyRate|money(currency) }}</td>
</tr>
{% endif %}
{% endif %}
{% endif %}
{% if entity.budget > 0 %}
{% set totalPercentReached = 100 %}
{% if entity.budget > 0 %}
{% set totalPercentReached = (stats.rateBillable / (entity.budget / 100)) %}
{% endif %}
<tr>
<th colspan="3" class="text-nowrap text-end">
<th colspan="2" class="text-nowrap text-end">
{% if totalPercentReached < 100 %}
{{ 'stats.percentUsedLeft'|trans({'%percent%': totalPercentReached|number_format(2), '%left%': (entity.budget - stats.rateBillable)|money(currency)}) }}
{% else %}
@@ -192,10 +131,9 @@
</div>
<div class="col-12 col-md-6 order-0 order-md-1 p-3">
{% if entity.budget > 0 %}
<div class="mb-3">{{ progress.progressbar(entity.budget, stats.rateBillable, rateTrans|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}</div>
<div class="mb-3">{{ progress.progressbar(entity.budget, stats.rateBillable, 'revenue'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}</div>
{% endif %}
<div class="mb-3">{{ progress.progressbar(stats.rate, stats.rateBillable, 'billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.rate|money(currency), true) }}</div>
<div class="mb-3">{{ progress.progressbar(stats.rateBillable, stats.internalRate, 'internalRate'|trans, stats.internalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}</div>
<div class="mb-3">{{ progress.progressbar(stats.rateBillable, stats.internalRate, 'costs'|trans, stats.internalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}</div>
</div>
</div>
{% endblock %}

View File

@@ -77,9 +77,9 @@
{% endif %}
{% if filterCount > 0 and not form.vars.data.isBookmarkSearch() %}
<button class="{{ searchBtnClass }} text-orange" type="submit" id="resetSearchFilter" name="resetSearchFilter" data-toggle="tooltip" title="{{ 'remove_filter'|trans }}">
<a class="{{ searchBtnClass }} text-orange" href="{{ path(app.request.attributes.get('_route')) }}" data-toggle="tooltip" title="{{ 'remove_filter'|trans }}">
{{ icon('cancel') }}
</button>
</a>
{% endif %}
</span>
</div>

View File

@@ -16,7 +16,7 @@
</button>
{% endif %}
{% if form.vars.data.countFilter() > 0 and not form.vars.data.isBookmarkSearch() %}
<button type="submit" id="resetSearchFilter" name="resetSearchFilter" class="btn btn-icon btn-outline-secondary" data-toggle="tooltip" title="{{ 'remove_filter'|trans }}">{{ icon('cancel', true) }}</button>
<a href="{{ path(app.request.attributes.get('_route')) }}" class="btn btn-icon btn-outline-secondary" data-toggle="tooltip" title="{{ 'remove_filter'|trans }}">{{ icon('cancel', true) }}</a>
{% endif %}
</div>
<button type="submit" name="performSearch" value="performSearch" class="performSearch btn btn-primary ms-auto" data-type="submit">{{ 'search'|trans }}</button>

View File

@@ -1,6 +1,6 @@
{% macro init_frontend_loader() %}
{% set fdow = app.user is not null ? app.user.firstDayOfWeek : 'monday' %}
{% set configurations = javascript_configurations(app.user)|merge({
{% set configurations = javascript_configurations(app.user, app.request.locale)|merge({
login: path('login'),
direction: tabler_bundle.rightToLeft ? 'rtl' : 'ltr',
first_dow_iso: iso_day_by_name(fdow),

View File

@@ -542,14 +542,6 @@
</div>
{% endmacro %}
{% macro percent(maximum, current) %}
{% if maximum > 0 %}
{{ (current / (maximum / 100))|number_format(2) }} %
{% else %}
{{ 0|number_format(2) }} %
{% endif %}
{% endmacro %}
{% macro page_reloader(events, full_reload) %}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {

View File

@@ -1,41 +1,30 @@
{% extends 'reporting/layout.html.twig' %}
{% import "macros/datatables.html.twig" as tables %}
{% set showMoneyBudget = is_granted('budget_money', 'project') %}
{% set showTimeBudget = is_granted('budget_time', 'project') %}
{% set viewRevenue = is_granted('view_rate_other_timesheet') %}
{% set columns = {
'name': {'class': 'alwaysVisible'},
'name': {'class': 'alwaysVisible'},
} %}
{% if showTimeBudget %}
{% if is_granted('budget_time', 'project') %}
{% set columns = columns|merge({
'timeBudget': {'class': 'd-none d-md-table-cell', 'title': 'timeBudget'|trans},
'timeBudget': {'class': 'd-none d-md-table-cell', 'title': 'timeBudget'|trans},
}) %}
{% endif %}
{% if showMoneyBudget %}
{% if is_granted('budget_money', 'project') %}
{% set columns = columns|merge({
'budget': {'class': 'd-none d-md-table-cell', 'title': 'budget'|trans},
'budget': {'class': 'd-none d-md-table-cell', 'title': 'budget'|trans},
}) %}
{% endif %}
{% set columns = columns|merge({
'duration': {'class': 'text-end hw-min w-min', 'title': 'stats.durationMonth'|trans, 'columnClass': 'w-min'},
'duration': {'class': 'text-center hw-min', 'title': 'duration'|trans, 'columnClass': 'w-min'},
}) %}
{% if showMoneyBudget %}
{% if is_granted('view_rate_other_timesheet') %}
{% set columns = columns|merge({
'rate': {'class': 'text-end hw-min w-min', 'title': 'stats.amountMonth'|trans, 'columnClass': 'w-min'},
'revenue': {'class': 'text-center hw-min', 'columnClass': 'w-min'},
'costs': {'class': 'text-center hw-min', 'columnClass': 'w-min'},
'profit': {'class': 'text-center hw-min', 'columnClass': 'w-min'},
}) %}
{% endif %}
{% set columns = columns|merge({
'duration': {'class': 'text-center hw-min', 'title': 'stats.durationMonth'|trans, 'columnClass': 'w-min'},
}) %}
{% if viewRevenue %}
{% set columns = columns|merge({
'rate': {'class': 'text-center hw-min', 'title': 'stats.amountMonth'|trans, 'columnClass': 'w-min'},
}) %}
{% endif %}
{% set columns = columns|merge({
'billable': {'class': 'd-none d-sm-table-cell text-center hw-min w-min', 'columnClass': 'w-min'},
'actions': {'class': 'actions alwaysVisible'},
}) %}
{% set tableName = 'project_daterange_reporting' %}
@@ -95,10 +84,12 @@
{{ 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 == 'revenue' %}
{{ entry.statistic.rateBillable|money(currency) }}
{% elseif name == 'costs' %}
{{ entry.statistic.internalRate|money(currency) }}
{% elseif name == 'profit' %}
{{ (entry.statistic.rateBillable - entry.statistic.internalRate)|money(currency) }}
{% elseif name == 'timeBudget' %}
{% if is_granted('time', project) %}
{{ progress.progressbar_timebudget(entry) }}

View File

@@ -98,7 +98,7 @@
<ul class="nav nav-pills" data-bs-toggle="tabs">
<li class="nav-item"><a class="nav-link active" data-chart="{{ chartPrefix }}Duration" href="#time-chart-{{ id }}" data-bs-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li>
{% if view_revenue %}
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Rate" href="#revenue-chart-{{ id }}" data-bs-toggle="tab">{{ 'stats.revenue'|trans }}</a></li>
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Rate" href="#revenue-chart-{{ id }}" data-bs-toggle="tab">{{ 'revenue'|trans }}</a></li>
{% endif %}
{% if see_users %}
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}User" href="#user-chart-{{ id}}" data-bs-toggle="tab">{{ 'user'|trans }}</a></li>
@@ -307,7 +307,7 @@
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Duration" href="#time-chart" data-bs-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li>
{% endif %}
{% if showTotalRevenueChart %}
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Rate" href="#revenue-chart" data-bs-toggle="tab">{{ 'stats.revenue'|trans }}</a></li>
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Rate" href="#revenue-chart" data-bs-toggle="tab">{{ 'revenue'|trans }}</a></li>
{% endif %}
{% if see_users and project_details.userStats|length > 0 %}
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}User" href="#user-chart" data-bs-toggle="tab">{{ 'user'|trans }}</a></li>

View File

@@ -17,9 +17,9 @@
'amountTotal': {'title': 'stats.amountTotal'|trans},
'billableMoney': {'title': 'billable'|trans},
'internalRate': {'title': 'internalRate'|trans},
'revenue': {'title': ('stats.revenue'|trans ~ ' (' ~ 'billable'|trans ~ ' - ' ~ 'internalRate'|trans ~ ')')},
'revenue': {'title': ('revenue'|trans ~ ' (' ~ 'billable'|trans ~ ' - ' ~ 'internalRate'|trans ~ ')')},
'hourlyBillable': {'title': ('hourlyRate'|trans ~ ' (' ~ 'billable'|trans ~ ' / ' ~ 'stats.durationTotal'|trans ~ ')')},
'hourlyTotal': {'title': ('hourlyRate'|trans ~ ' (' ~ 'stats.revenue'|trans ~ ' / ' ~ 'stats.durationTotal'|trans ~ ')')},
'hourlyTotal': {'title': ('hourlyRate'|trans ~ ' (' ~ 'revenue'|trans ~ ' / ' ~ 'stats.durationTotal'|trans ~ ')')},
'invoiced': {'title': 'not_invoiced'|trans},
}) %}
{% endif %}

View File

@@ -1,4 +1,5 @@
{%- set with_avatar = avatar is defined ? avatar : true -%}
{%- set show_empty_rows = true -%}
{%- set absoluteDuration = 0 -%}
{%- set absoluteInternalRate = 0 -%}
{%- set absoluteRate = 0 -%}
@@ -38,7 +39,7 @@
{% set usersTotalDuration = 0 %}
{% set usersTotalInternalRate = 0 %}
{% set usersTotalRate = 0 %}
{% for period in attribute(userPeriod, period_attribute) %}
{% for period in userPeriod.data %}
{% set usersTotalDuration = usersTotalDuration + period.totalDuration %}
{% set absoluteDuration = absoluteDuration + period.totalDuration %}
{% set usersTotalInternalRate = usersTotalInternalRate + period.totalInternalRate %}
@@ -50,7 +51,7 @@
{% set totalsInternalRate = totalsInternalRate|merge({(reportDateKey): (totalsInternalRate[reportDateKey] + period.totalInternalRate)}) %}
{% set totalsRate = totalsRate|merge({(reportDateKey): (totalsRate[reportDateKey] + period.totalRate)}) %}
{% endfor %}
{% if userPeriod.user.enabled or usersTotalDuration != 0 %}
{% if (show_empty_rows and userPeriod.user.enabled) or usersTotalDuration != 0 %}
<tr class="user">
{% if with_avatar %}
<td class="w-avatar">
@@ -73,7 +74,7 @@
{% block total_duration_user %}{% endblock %}
{% endif %}
</td>
{% for period in attribute(userPeriod, period_attribute) %}
{% for period in userPeriod.data %}
<td class="text-nowrap text-center day-total{% block period_cell_class %}{% endblock %}"{% block period_cell_attribute %}{% endblock %}>
{% if period.totalDuration != 0 or period.totalRate != 0 or period.totalInternalRate != 0 %}
{% if dataType == 'rate' %}

View File

@@ -1,16 +0,0 @@
{% extends 'security/password-reset/layout.html.twig' %}
{% block login_form %}
{{ form_start(form, { 'action': path('resetting_reset', {'token': token}), 'attr': { 'class': 'resetting_reset' } }) }}
{{ form_widget(form) }}
<div class="row">
<div class="col-xs-12">
<button type="submit" class="btn btn-primary">{{ 'Reset your password'|trans({}, 'TablerBundle') }}</button>
</div>
</div>
{{ form_end(form) }}
{% endblock %}

View File

@@ -32,12 +32,6 @@
'stats.durationTotal' : stats.durationTotal|duration,
'stats.durationMonth' : stats.durationThisMonth|duration
}) %}
{#% if seeOwnRate %}
{% set datagrid = datagrid|merge({
'stats.amountMonth' : stats.rateThisMonthBillable|money,
'stats.amountTotal' : stats.rateTotalBillable|money
}) %}
{% endif %#}
{% endif %}
{% if not seeOwnRate and is_granted('hourly-rate', user) and user.preferenceValue('hourly_rate') is not null %}
{% set datagrid = datagrid|merge({