added new rounding mode: closest (#611)
This commit is contained in:
@@ -19,13 +19,14 @@ kimai:
|
|||||||
|
|
||||||
# Rounding rules are used to round the begin & end dates and the duration for timesheet records.
|
# Rounding rules are used to round the begin & end dates and the duration for timesheet records.
|
||||||
# The "default" rule will round "begin" down and "end" up to the full minute, the "duration" will not be rounded.
|
# The "default" rule will round "begin" down and "end" up to the full minute, the "duration" will not be rounded.
|
||||||
# Please read var/docs/configurations.md to find out more about rounding rules
|
# Find out more about rounding rules at https://www.kimai.org/documentation/timesheet.html
|
||||||
rounding:
|
rounding:
|
||||||
default:
|
default:
|
||||||
days: ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']
|
days: ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']
|
||||||
begin: 1
|
begin: 1
|
||||||
end: 1
|
end: 1
|
||||||
duration: 0
|
duration: 0
|
||||||
|
mode: default
|
||||||
|
|
||||||
# If you want to apply different hourly rates for specific weekdays, you can uncomment the "rates" configuration.
|
# If you want to apply different hourly rates for specific weekdays, you can uncomment the "rates" configuration.
|
||||||
# The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays".
|
# The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays".
|
||||||
@@ -69,7 +70,7 @@ kimai:
|
|||||||
# currency: EUR
|
# currency: EUR
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------
|
||||||
# Free configurable permission system, see var/docs/permissions.md
|
# Find out more about the configurable permission system at https://www.kimai.org/documentation/permissions.html
|
||||||
permissions:
|
permissions:
|
||||||
sets:
|
sets:
|
||||||
# mapping complex rulesets of single permissions to named "sets" ("set name" = [array of "permissions"])
|
# mapping complex rulesets of single permissions to named "sets" ("set name" = [array of "permissions"])
|
||||||
@@ -186,7 +187,7 @@ kimai:
|
|||||||
end: '20:00'
|
end: '20:00'
|
||||||
|
|
||||||
# You can configure unlimited google calendars to display events for your company (e.g. holidays)
|
# You can configure unlimited google calendars to display events for your company (e.g. holidays)
|
||||||
# Please read var/docs/configurations.md to find out more about calendar integration
|
# Find out more about calendar integration at https://www.kimai.org/documentation/calendar.html
|
||||||
# google:
|
# google:
|
||||||
# api_key: 'your-restricted-google-api-key'
|
# api_key: 'your-restricted-google-api-key'
|
||||||
# sources:
|
# sources:
|
||||||
@@ -235,7 +236,7 @@ kimai:
|
|||||||
widgets: [amountToday, amountWeek, amountMonth, amountYear]
|
widgets: [amountToday, amountWeek, amountMonth, amountYear]
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------
|
||||||
# All available widgets, please see documentation at var/docs/dashboard.md
|
# Find out more about dashboard widgets at https://www.kimai.org/documentation/dashboard.html
|
||||||
widgets:
|
widgets:
|
||||||
userDurationToday: { title: stats.durationToday, query: duration, user: true, begin: '00:00:00', end: '23:59:59', icon: duration, color: green }
|
userDurationToday: { title: stats.durationToday, query: duration, user: true, begin: '00:00:00', end: '23:59:59', icon: duration, color: green }
|
||||||
userDurationWeek: { title: stats.durationWeek, query: duration, user: true, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: duration, color: blue }
|
userDurationWeek: { title: stats.durationWeek, query: duration, user: true, begin: 'monday this week 00:00:00', end: 'sunday this week 23:59:59', icon: duration, color: blue }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace App\DependencyInjection;
|
|||||||
|
|
||||||
use App\Model\DashboardSection;
|
use App\Model\DashboardSection;
|
||||||
use App\Model\Widget;
|
use App\Model\Widget;
|
||||||
|
use App\Timesheet\Rounding\RoundingInterface;
|
||||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||||
|
|
||||||
@@ -81,6 +82,21 @@ class Configuration implements ConfigurationInterface
|
|||||||
->integerNode('duration')
|
->integerNode('duration')
|
||||||
->defaultValue(0)
|
->defaultValue(0)
|
||||||
->end()
|
->end()
|
||||||
|
->scalarNode('mode')
|
||||||
|
->defaultValue('default')
|
||||||
|
->validate()
|
||||||
|
->thenInvalid('Chosen rounding mode is invalid')
|
||||||
|
->ifTrue(function ($value) {
|
||||||
|
$class = 'App\\Timesheet\\Rounding\\' . ucfirst($value) . 'Rounding';
|
||||||
|
if (class_exists($class)) {
|
||||||
|
$rounding = new $class();
|
||||||
|
return !($rounding instanceof RoundingInterface);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
->thenInvalid('Chosen rounding mode is invalid')
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->defaultValue([])
|
->defaultValue([])
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace App\Timesheet\Calculator;
|
|||||||
|
|
||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
use App\Timesheet\CalculatorInterface;
|
use App\Timesheet\CalculatorInterface;
|
||||||
|
use App\Timesheet\Rounding\RoundingInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation to calculate the durations for a timesheet record.
|
* Implementation to calculate the durations for a timesheet record.
|
||||||
@@ -66,74 +67,14 @@ class DurationCalculator implements CalculatorInterface
|
|||||||
$days = array_map('strtolower', $rounding['days']);
|
$days = array_map('strtolower', $rounding['days']);
|
||||||
|
|
||||||
if (in_array(strtolower($weekday), $days)) {
|
if (in_array(strtolower($weekday), $days)) {
|
||||||
$this->roundBegin($record, $rounding['begin']);
|
$class = 'App\\Timesheet\\Rounding\\' . ucfirst($rounding['mode']) . 'Rounding';
|
||||||
$this->roundEnd($record, $rounding['end']);
|
/* @var $rounder RoundingInterface */
|
||||||
|
$rounder = new $class();
|
||||||
|
$rounder->roundBegin($record, $rounding['begin']);
|
||||||
|
$rounder->roundEnd($record, $rounding['end']);
|
||||||
$this->applyDuration($record);
|
$this->applyDuration($record);
|
||||||
$this->roundDuration($record, $rounding['duration']);
|
$rounder->roundDuration($record, $rounding['duration']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Timesheet $record
|
|
||||||
* @param int $minutes
|
|
||||||
*/
|
|
||||||
protected function roundBegin(Timesheet $record, $minutes)
|
|
||||||
{
|
|
||||||
if ($minutes <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$timestamp = $record->getBegin()->getTimestamp();
|
|
||||||
$seconds = $minutes * 60;
|
|
||||||
$diff = $timestamp % $seconds;
|
|
||||||
|
|
||||||
if (0 === $diff) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$record->getBegin()->setTimestamp($timestamp - $diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Timesheet $record
|
|
||||||
* @param int $minutes
|
|
||||||
*/
|
|
||||||
protected function roundEnd(Timesheet $record, $minutes)
|
|
||||||
{
|
|
||||||
if ($minutes <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$timestamp = $record->getEnd()->getTimestamp();
|
|
||||||
$seconds = $minutes * 60;
|
|
||||||
$diff = $timestamp % $seconds;
|
|
||||||
|
|
||||||
if (0 === $diff) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$record->getEnd()->setTimestamp($timestamp - $diff + $seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Timesheet $record
|
|
||||||
* @param int $minutes
|
|
||||||
*/
|
|
||||||
protected function roundDuration(Timesheet $record, $minutes)
|
|
||||||
{
|
|
||||||
if ($minutes <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$timestamp = $record->getDuration();
|
|
||||||
$seconds = $minutes * 60;
|
|
||||||
$diff = $timestamp % $seconds;
|
|
||||||
|
|
||||||
if (0 === $diff) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$record->setDuration($timestamp - $diff + $seconds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
90
src/Timesheet/Rounding/ClosestRounding.php
Normal file
90
src/Timesheet/Rounding/ClosestRounding.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Timesheet\Rounding;
|
||||||
|
|
||||||
|
use App\Entity\Timesheet;
|
||||||
|
|
||||||
|
class ClosestRounding implements RoundingInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundBegin(Timesheet $record, $minutes)
|
||||||
|
{
|
||||||
|
if ($minutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = $record->getBegin()->getTimestamp();
|
||||||
|
$seconds = $minutes * 60;
|
||||||
|
$diff = $timestamp % $seconds;
|
||||||
|
|
||||||
|
if (0 === $diff) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff > ($seconds / 2)) {
|
||||||
|
$record->getBegin()->setTimestamp($timestamp - $diff + $seconds);
|
||||||
|
} else {
|
||||||
|
$record->getBegin()->setTimestamp($timestamp - $diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundEnd(Timesheet $record, $minutes)
|
||||||
|
{
|
||||||
|
if ($minutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = $record->getEnd()->getTimestamp();
|
||||||
|
$seconds = $minutes * 60;
|
||||||
|
$diff = $timestamp % $seconds;
|
||||||
|
|
||||||
|
if (0 === $diff) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff > ($seconds / 2)) {
|
||||||
|
$record->getEnd()->setTimestamp($timestamp - $diff + $seconds);
|
||||||
|
} else {
|
||||||
|
$record->getEnd()->setTimestamp($timestamp - $diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundDuration(Timesheet $record, $minutes)
|
||||||
|
{
|
||||||
|
if ($minutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = $record->getDuration();
|
||||||
|
$seconds = $minutes * 60;
|
||||||
|
$diff = $timestamp % $seconds;
|
||||||
|
|
||||||
|
if (0 === $diff) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff > ($seconds / 2)) {
|
||||||
|
$record->setDuration($timestamp - $diff + $seconds);
|
||||||
|
} else {
|
||||||
|
$record->setDuration($timestamp - $diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/Timesheet/Rounding/DefaultRounding.php
Normal file
78
src/Timesheet/Rounding/DefaultRounding.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Timesheet\Rounding;
|
||||||
|
|
||||||
|
use App\Entity\Timesheet;
|
||||||
|
|
||||||
|
class DefaultRounding implements RoundingInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundBegin(Timesheet $record, $minutes)
|
||||||
|
{
|
||||||
|
if ($minutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = $record->getBegin()->getTimestamp();
|
||||||
|
$seconds = $minutes * 60;
|
||||||
|
$diff = $timestamp % $seconds;
|
||||||
|
|
||||||
|
if (0 === $diff) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$record->getBegin()->setTimestamp($timestamp - $diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundEnd(Timesheet $record, $minutes)
|
||||||
|
{
|
||||||
|
if ($minutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = $record->getEnd()->getTimestamp();
|
||||||
|
$seconds = $minutes * 60;
|
||||||
|
$diff = $timestamp % $seconds;
|
||||||
|
|
||||||
|
if (0 === $diff) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$record->getEnd()->setTimestamp($timestamp - $diff + $seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundDuration(Timesheet $record, $minutes)
|
||||||
|
{
|
||||||
|
if ($minutes <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = $record->getDuration();
|
||||||
|
$seconds = $minutes * 60;
|
||||||
|
$diff = $timestamp % $seconds;
|
||||||
|
|
||||||
|
if (0 === $diff) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$record->setDuration($timestamp - $diff + $seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Timesheet/Rounding/RoundingInterface.php
Normal file
37
src/Timesheet/Rounding/RoundingInterface.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Timesheet\Rounding;
|
||||||
|
|
||||||
|
use App\Entity\Timesheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply rounding rules to the given timesheet.
|
||||||
|
*/
|
||||||
|
interface RoundingInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundBegin(Timesheet $record, $minutes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param int $minutes
|
||||||
|
*/
|
||||||
|
public function roundEnd(Timesheet $record, $minutes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Timesheet $record
|
||||||
|
* @param $minutes
|
||||||
|
*/
|
||||||
|
public function roundDuration(Timesheet $record, $minutes);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
'username': 'hidden-xs',
|
'username': 'hidden-xs',
|
||||||
'email': 'hidden-xs hidden-sm',
|
'email': 'hidden-xs hidden-sm',
|
||||||
'title': 'hidden-xs',
|
'title': 'hidden-xs',
|
||||||
'active': '',
|
|
||||||
'roles': '',
|
'roles': '',
|
||||||
|
'active': '',
|
||||||
'actions': 'alwaysVisible',
|
'actions': 'alwaysVisible',
|
||||||
} %}
|
} %}
|
||||||
|
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}">{{ entry.username }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}">{{ entry.username }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'email') }}">{{ entry.email }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'email') }}">{{ entry.email }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'title') }}">{{ entry.title }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'title') }}">{{ entry.title }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'active') }}">{{ widgets.label_visible(entry.enabled) }}</td>
|
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'roles') }}">
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'roles') }}">
|
||||||
{% for role in entry.roles %}
|
{% for role in entry.roles %}
|
||||||
{{ widgets.label_role(role) }}
|
{{ widgets.label_role(role) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'active') }}">{{ widgets.label_visible(entry.enabled) }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% set actionButtons = {} %}
|
{% set actionButtons = {} %}
|
||||||
{% if is_granted('edit', entry) %}
|
{% if is_granted('edit', entry) %}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 15,
|
'begin' => 15,
|
||||||
'end' => 15,
|
'end' => 15,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 17, 35),
|
(clone $start)->setTime(12, 17, 35),
|
||||||
@@ -77,6 +78,7 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 17, 35),
|
(clone $start)->setTime(12, 17, 35),
|
||||||
@@ -90,6 +92,7 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 1,
|
'begin' => 1,
|
||||||
'end' => 1,
|
'end' => 1,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 17, 35),
|
(clone $start)->setTime(12, 17, 35),
|
||||||
@@ -103,6 +106,7 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 30,
|
'duration' => 30,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 10, 51),
|
(clone $start)->setTime(12, 10, 51),
|
||||||
@@ -116,12 +120,14 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 15,
|
'begin' => 15,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
'weekdays' => [
|
'weekdays' => [
|
||||||
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 1,
|
'end' => 1,
|
||||||
'duration' => 30,
|
'duration' => 30,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 27, 35), // 12:15
|
(clone $start)->setTime(12, 27, 35), // 12:15
|
||||||
@@ -135,12 +141,14 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 15,
|
'begin' => 15,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 30,
|
'duration' => 30,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
'weekdays' => [
|
'weekdays' => [
|
||||||
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 1,
|
'end' => 1,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 27, 35), // 12:15
|
(clone $start)->setTime(12, 27, 35), // 12:15
|
||||||
@@ -154,12 +162,14 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 1,
|
'duration' => 1,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
'weekdays' => [
|
'weekdays' => [
|
||||||
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 1,
|
'duration' => 1,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
@@ -173,12 +183,14 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 1,
|
'begin' => 1,
|
||||||
'end' => 1,
|
'end' => 1,
|
||||||
'duration' => 1,
|
'duration' => 1,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
'weekdays' => [
|
'weekdays' => [
|
||||||
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||||
'begin' => 1,
|
'begin' => 1,
|
||||||
'end' => 1,
|
'end' => 1,
|
||||||
'duration' => 1,
|
'duration' => 1,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 27, 00), // no diff, to test ...
|
(clone $start)->setTime(12, 27, 00), // no diff, to test ...
|
||||||
@@ -192,12 +204,14 @@ class DurationCalculatorTest extends TestCase
|
|||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
'weekdays' => [
|
'weekdays' => [
|
||||||
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
'days' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||||
'begin' => 0,
|
'begin' => 0,
|
||||||
'end' => 0,
|
'end' => 0,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
'mode' => 'default',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
|||||||
152
tests/Timesheet/Rounding/ClosestRoundingTest.php
Normal file
152
tests/Timesheet/Rounding/ClosestRoundingTest.php
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\Timesheet\Calculator;
|
||||||
|
|
||||||
|
use App\Entity\Timesheet;
|
||||||
|
use App\Timesheet\Rounding\ClosestRounding;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Timesheet\Rounding\ClosestRounding
|
||||||
|
*/
|
||||||
|
class ClosestRoundingTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider getTestData
|
||||||
|
*/
|
||||||
|
public function testCalculate($roundBegin, $roundEnd, $roundDuration, \DateTime $start, \DateTime $end, \DateTime $expectedStart, \DateTime $expectedEnd, $expectedDuration)
|
||||||
|
{
|
||||||
|
$record = new Timesheet();
|
||||||
|
$record->setBegin($start);
|
||||||
|
$record->setEnd($end);
|
||||||
|
$this->assertEquals(0, $record->getDuration());
|
||||||
|
|
||||||
|
$record->setDuration($record->getEnd()->getTimestamp() - $record->getBegin()->getTimestamp());
|
||||||
|
|
||||||
|
$sut = new ClosestRounding();
|
||||||
|
$sut->roundBegin($record, $roundBegin);
|
||||||
|
$sut->roundEnd($record, $roundEnd);
|
||||||
|
$record->setDuration($record->getEnd()->getTimestamp() - $record->getBegin()->getTimestamp());
|
||||||
|
$sut->roundDuration($record, $roundDuration);
|
||||||
|
|
||||||
|
$this->assertEquals($expectedStart->getTimestamp(), $record->getBegin()->getTimestamp());
|
||||||
|
$this->assertEquals($expectedEnd->getTimestamp(), $record->getEnd()->getTimestamp());
|
||||||
|
$this->assertEquals($expectedDuration, $record->getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTestData()
|
||||||
|
{
|
||||||
|
$start = new \DateTime();
|
||||||
|
$start->setTime(12, 0, 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
$start,
|
||||||
|
(clone $start)->setTimestamp($start->getTimestamp() + 1837),
|
||||||
|
$start,
|
||||||
|
(clone $start)->setTimestamp($start->getTimestamp() + 1837),
|
||||||
|
1837
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15,
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
(clone $start)->setTime(12, 15, 00),
|
||||||
|
(clone $start)->setTime(13, 30, 00),
|
||||||
|
4500
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
4517
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
(clone $start)->setTime(12, 18, 00),
|
||||||
|
(clone $start)->setTime(13, 33, 00),
|
||||||
|
4500
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
30,
|
||||||
|
(clone $start)->setTime(12, 10, 51),
|
||||||
|
(clone $start)->setTime(14, 40, 52),
|
||||||
|
(clone $start)->setTime(12, 10, 51),
|
||||||
|
(clone $start)->setTime(14, 40, 52),
|
||||||
|
9000
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // 12:15
|
||||||
|
(clone $start)->setTime(14, 32, 52), // 14:33 => 2:18 => 2:30
|
||||||
|
(clone $start)->setTime(12, 27, 35),
|
||||||
|
(clone $start)->setTime(14, 33, 00),
|
||||||
|
7200
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
30,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // 12:15
|
||||||
|
(clone $start)->setTime(14, 32, 52), // 14:33 => 2:18 (second duration will not be rounded)
|
||||||
|
(clone $start)->setTime(12, 30, 00), // 12:15
|
||||||
|
(clone $start)->setTime(14, 32, 52), // 14:33 => 2:18 (second duration will not be rounded)
|
||||||
|
7200
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
(clone $start)->setTime(12, 27, 00), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 00), // ... that no rounding is applied
|
||||||
|
(clone $start)->setTime(12, 27, 00), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 00), // ... that no rounding is applied
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
0
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
152
tests/Timesheet/Rounding/DefaultRoundingTest.php
Normal file
152
tests/Timesheet/Rounding/DefaultRoundingTest.php
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\Timesheet\Calculator;
|
||||||
|
|
||||||
|
use App\Entity\Timesheet;
|
||||||
|
use App\Timesheet\Rounding\DefaultRounding;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Timesheet\Rounding\DefaultRounding
|
||||||
|
*/
|
||||||
|
class DefaultRoundingTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider getTestData
|
||||||
|
*/
|
||||||
|
public function testCalculate($roundBegin, $roundEnd, $roundDuration, \DateTime $start, \DateTime $end, \DateTime $expectedStart, \DateTime $expectedEnd, $expectedDuration)
|
||||||
|
{
|
||||||
|
$record = new Timesheet();
|
||||||
|
$record->setBegin($start);
|
||||||
|
$record->setEnd($end);
|
||||||
|
$this->assertEquals(0, $record->getDuration());
|
||||||
|
|
||||||
|
$record->setDuration($record->getEnd()->getTimestamp() - $record->getBegin()->getTimestamp());
|
||||||
|
|
||||||
|
$sut = new DefaultRounding();
|
||||||
|
$sut->roundBegin($record, $roundBegin);
|
||||||
|
$sut->roundEnd($record, $roundEnd);
|
||||||
|
$record->setDuration($record->getEnd()->getTimestamp() - $record->getBegin()->getTimestamp());
|
||||||
|
$sut->roundDuration($record, $roundDuration);
|
||||||
|
|
||||||
|
$this->assertEquals($expectedStart->getTimestamp(), $record->getBegin()->getTimestamp());
|
||||||
|
$this->assertEquals($expectedEnd->getTimestamp(), $record->getEnd()->getTimestamp());
|
||||||
|
$this->assertEquals($expectedDuration, $record->getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTestData()
|
||||||
|
{
|
||||||
|
$start = new \DateTime();
|
||||||
|
$start->setTime(12, 0, 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
$start,
|
||||||
|
(clone $start)->setTimestamp($start->getTimestamp() + 1837),
|
||||||
|
$start,
|
||||||
|
(clone $start)->setTimestamp($start->getTimestamp() + 1837),
|
||||||
|
1837
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15,
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
(clone $start)->setTime(12, 15, 00),
|
||||||
|
(clone $start)->setTime(13, 45, 00),
|
||||||
|
5400
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
4517
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 17, 35),
|
||||||
|
(clone $start)->setTime(13, 32, 52),
|
||||||
|
(clone $start)->setTime(12, 17, 00),
|
||||||
|
(clone $start)->setTime(13, 33, 00),
|
||||||
|
4560
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
30,
|
||||||
|
(clone $start)->setTime(12, 10, 51),
|
||||||
|
(clone $start)->setTime(14, 40, 52),
|
||||||
|
(clone $start)->setTime(12, 10, 51),
|
||||||
|
(clone $start)->setTime(14, 40, 52),
|
||||||
|
10800
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
30,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // 12:15
|
||||||
|
(clone $start)->setTime(14, 32, 52), // 14:33 => 2:18 => 2:30
|
||||||
|
(clone $start)->setTime(12, 27, 35),
|
||||||
|
(clone $start)->setTime(14, 33, 00),
|
||||||
|
9000
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
30,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // 12:15
|
||||||
|
(clone $start)->setTime(14, 32, 52), // 14:33 => 2:18 (second duration will not be rounded)
|
||||||
|
(clone $start)->setTime(12, 15, 00), // 12:15
|
||||||
|
(clone $start)->setTime(14, 32, 52), // 14:33 => 2:18 (second duration will not be rounded)
|
||||||
|
9000
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
(clone $start)->setTime(12, 27, 00), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 00), // ... that no rounding is applied
|
||||||
|
(clone $start)->setTime(12, 27, 00), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 00), // ... that no rounding is applied
|
||||||
|
0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
(clone $start)->setTime(12, 27, 35), // no diff, to test ...
|
||||||
|
(clone $start)->setTime(12, 27, 35), // ... that no rounding is applied
|
||||||
|
0
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user