respect users timezone when populating working-time dashboard widget (#988)
This commit is contained in:
@@ -275,37 +275,86 @@ class TimesheetRepository extends EntityRepository
|
|||||||
* @param User|null $user
|
* @param User|null $user
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getDailyData(DateTime $begin, DateTime $end, ?User $user = null)
|
protected function getDailyData(DateTime $begin, DateTime $end, ?User $user = null)
|
||||||
{
|
{
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$query = new TimesheetQuery();
|
||||||
|
$query
|
||||||
|
->setBegin($begin)
|
||||||
|
->setEnd($end)
|
||||||
|
->setUser($user)
|
||||||
|
->setState(TimesheetQuery::STATE_STOPPED)
|
||||||
|
;
|
||||||
|
$timesheets = $this->getTimesheetsForQuery($query);
|
||||||
|
|
||||||
$qb
|
$results = [];
|
||||||
->addSelect('SUM(t.rate) as rate')
|
/** @var Timesheet $result */
|
||||||
->addSelect('SUM(t.duration) as duration')
|
foreach ($timesheets as $result) {
|
||||||
->addSelect('MONTH(t.begin) as month')
|
$timezone = new \DateTimeZone($result->getTimezone());
|
||||||
->addSelect('YEAR(t.begin) as year')
|
/** @var \DateTime $beginTmp */
|
||||||
->addSelect('DAY(t.begin) as day')
|
$beginTmp = $result->getBegin();
|
||||||
->from(Timesheet::class, 't')
|
$beginTmp->setTimezone($timezone);
|
||||||
->andWhere($qb->expr()->gte('t.begin', ':from'))
|
/** @var DateTime $endTmp */
|
||||||
->setParameter('from', $begin, Type::DATETIME)
|
$endTmp = $result->getEnd();
|
||||||
->andWhere($qb->expr()->lte('t.end', ':to'))
|
$endTmp->setTimezone($timezone);
|
||||||
->setParameter('to', $end, Type::DATETIME);
|
$dateKeyEnd = $endTmp->format('Ymd');
|
||||||
|
|
||||||
if (null !== $user) {
|
do {
|
||||||
$qb->andWhere('t.user = :user')
|
$dateKey = $beginTmp->format('Ymd');
|
||||||
->setParameter('user', $user);
|
|
||||||
|
if (!isset($results[$dateKey])) {
|
||||||
|
$results[$dateKey] = [
|
||||||
|
'rate' => 0,
|
||||||
|
'duration' => 0,
|
||||||
|
'month' => $beginTmp->format('n'),
|
||||||
|
'year' => $beginTmp->format('Y'),
|
||||||
|
'day' => $beginTmp->format('j'),
|
||||||
|
'details' => []
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$qb
|
if ($dateKey !== $dateKeyEnd) {
|
||||||
->addGroupBy('year')
|
$newDateBegin = clone $beginTmp;
|
||||||
->addGroupBy('month')
|
$newDateBegin->add(new \DateInterval('P1D'));
|
||||||
->addGroupBy('day')
|
$newDateBegin->setTime(0, 0, 0);
|
||||||
->addOrderBy('year', 'DESC')
|
} else {
|
||||||
->addOrderBy('month', 'ASC')
|
$newDateBegin = clone $endTmp;
|
||||||
->addOrderBy('day', 'ASC')
|
}
|
||||||
;
|
|
||||||
|
|
||||||
return $qb->getQuery()->execute();
|
$duration = $newDateBegin->getTimestamp() - $beginTmp->getTimestamp();
|
||||||
|
$durationPercent = $duration / $result->getDuration();
|
||||||
|
$rate = $result->getRate() * $durationPercent;
|
||||||
|
|
||||||
|
$results[$dateKey]['rate'] += $rate;
|
||||||
|
$results[$dateKey]['duration'] += $duration;
|
||||||
|
$detailsId = $result->getProject()->getCustomer()->getId() . '_' . $result->getProject()->getId();
|
||||||
|
if (!isset($results[$dateKey]['details'][$detailsId])) {
|
||||||
|
$results[$dateKey]['details'][$detailsId] = [
|
||||||
|
'project' => $result->getProject(),
|
||||||
|
'activity' => $result->getActivity(),
|
||||||
|
'duration' => 0,
|
||||||
|
'rate' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$results[$dateKey]['details'][$detailsId]['duration'] += $duration;
|
||||||
|
$results[$dateKey]['details'][$detailsId]['rate'] += $rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$beginTmp = $newDateBegin;
|
||||||
|
|
||||||
|
if ((int) $end->format('Ymd') < (int) $newDateBegin->format('Ymd')) {
|
||||||
|
break 1;
|
||||||
|
}
|
||||||
|
} while ($dateKey !== $dateKeyEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($results);
|
||||||
|
|
||||||
|
foreach ($results as $key => $value) {
|
||||||
|
$results[$key]['details'] = array_values($results[$key]['details']);
|
||||||
|
}
|
||||||
|
$results = array_values($results);
|
||||||
|
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -334,7 +383,9 @@ class TimesheetRepository extends EntityRepository
|
|||||||
foreach ($results as $statRow) {
|
foreach ($results as $statRow) {
|
||||||
$dateTime = new DateTime();
|
$dateTime = new DateTime();
|
||||||
$dateTime->setDate($statRow['year'], $statRow['month'], $statRow['day']);
|
$dateTime->setDate($statRow['year'], $statRow['month'], $statRow['day']);
|
||||||
$days[$dateTime->format('Ymd')] = new Day($dateTime, (int) $statRow['duration'], (float) $statRow['rate']);
|
$dateTime->setTime(0, 0, 0);
|
||||||
|
$day = new Day($dateTime, (int) $statRow['duration'], (float) $statRow['rate']);
|
||||||
|
$days[$dateTime->format('Ymd')] = $day;
|
||||||
}
|
}
|
||||||
|
|
||||||
ksort($days);
|
ksort($days);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace App\Widget\Type;
|
|||||||
|
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
use App\Security\CurrentUser;
|
use App\Security\CurrentUser;
|
||||||
|
use App\Timesheet\UserDateTimeFactory;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
|
||||||
class DailyWorkingTimeChart extends SimpleWidget
|
class DailyWorkingTimeChart extends SimpleWidget
|
||||||
@@ -21,10 +22,15 @@ class DailyWorkingTimeChart extends SimpleWidget
|
|||||||
* @var TimesheetRepository
|
* @var TimesheetRepository
|
||||||
*/
|
*/
|
||||||
protected $repository;
|
protected $repository;
|
||||||
|
/**
|
||||||
|
* @var UserDateTimeFactory
|
||||||
|
*/
|
||||||
|
private $dateTimeFactory;
|
||||||
|
|
||||||
public function __construct(TimesheetRepository $repository, CurrentUser $user)
|
public function __construct(TimesheetRepository $repository, CurrentUser $user, UserDateTimeFactory $dateTime)
|
||||||
{
|
{
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
|
$this->dateTimeFactory = $dateTime;
|
||||||
$this->setId('DailyWorkingTimeChart');
|
$this->setId('DailyWorkingTimeChart');
|
||||||
$this->setTitle('stats.yourWorkingHours');
|
$this->setTitle('stats.yourWorkingHours');
|
||||||
$this->setOptions([
|
$this->setOptions([
|
||||||
@@ -57,8 +63,8 @@ class DailyWorkingTimeChart extends SimpleWidget
|
|||||||
$options = $this->getOptions($options);
|
$options = $this->getOptions($options);
|
||||||
|
|
||||||
$user = $options['user'];
|
$user = $options['user'];
|
||||||
$begin = new DateTime($options['begin']);
|
$begin = new DateTime($options['begin'], $this->dateTimeFactory->getTimezone());
|
||||||
$end = new DateTime($options['end']);
|
$end = new DateTime($options['end'], $this->dateTimeFactory->getTimezone());
|
||||||
|
|
||||||
return $this->repository->getDailyStats($user, $begin, $end);
|
return $this->repository->getDailyStats($user, $begin, $end);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ document.addEventListener('kimai.initialized', function() {
|
|||||||
data: {
|
data: {
|
||||||
labels: [
|
labels: [
|
||||||
{% for day in data -%}
|
{% for day in data -%}
|
||||||
moment('{{ day.day|date_format(constant('\DateTime::ISO8601')) }}').format('ll')
|
moment('{{ day.day|date_format('Y-m-d') }}').format('ll')
|
||||||
{% if not loop.last %},{% endif -%}
|
{% if not loop.last %},{% endif -%}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use App\Entity\User;
|
|||||||
use App\Model\Statistic\Day;
|
use App\Model\Statistic\Day;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
use App\Security\CurrentUser;
|
use App\Security\CurrentUser;
|
||||||
|
use App\Tests\Mocks\Security\UserDateTimeFactoryFactory;
|
||||||
use App\Widget\Type\AbstractWidgetType;
|
use App\Widget\Type\AbstractWidgetType;
|
||||||
use App\Widget\Type\DailyWorkingTimeChart;
|
use App\Widget\Type\DailyWorkingTimeChart;
|
||||||
use App\Widget\Type\SimpleWidget;
|
use App\Widget\Type\SimpleWidget;
|
||||||
@@ -31,8 +32,9 @@ class DailyWorkingTimeChartTest extends TestCase
|
|||||||
$repository = $this->getMockBuilder(TimesheetRepository::class)->disableOriginalConstructor()->getMock();
|
$repository = $this->getMockBuilder(TimesheetRepository::class)->disableOriginalConstructor()->getMock();
|
||||||
$user = $this->getMockBuilder(CurrentUser::class)->disableOriginalConstructor()->setMethods(['getUser'])->getMock();
|
$user = $this->getMockBuilder(CurrentUser::class)->disableOriginalConstructor()->setMethods(['getUser'])->getMock();
|
||||||
$user->expects($this->once())->method('getUser')->willReturn(new User());
|
$user->expects($this->once())->method('getUser')->willReturn(new User());
|
||||||
|
$mockFactory = new UserDateTimeFactoryFactory($this);
|
||||||
|
|
||||||
return new DailyWorkingTimeChart($repository, $user);
|
return new DailyWorkingTimeChart($repository, $user, $mockFactory->create('Europe/Berlin'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExtendsSimpleWidget()
|
public function testExtendsSimpleWidget()
|
||||||
@@ -106,8 +108,9 @@ class DailyWorkingTimeChartTest extends TestCase
|
|||||||
});
|
});
|
||||||
$user = $this->getMockBuilder(CurrentUser::class)->disableOriginalConstructor()->setMethods(['getUser'])->getMock();
|
$user = $this->getMockBuilder(CurrentUser::class)->disableOriginalConstructor()->setMethods(['getUser'])->getMock();
|
||||||
$user->expects($this->once())->method('getUser')->willReturn((new User())->setUsername('tralalala'));
|
$user->expects($this->once())->method('getUser')->willReturn((new User())->setUsername('tralalala'));
|
||||||
|
$mockFactory = new UserDateTimeFactoryFactory($this);
|
||||||
|
|
||||||
$sut = new DailyWorkingTimeChart($repository, $user);
|
$sut = new DailyWorkingTimeChart($repository, $user, $mockFactory->create('Europe/Berlin'));
|
||||||
$data = $sut->getData([]);
|
$data = $sut->getData([]);
|
||||||
self::assertCount(7, $data);
|
self::assertCount(7, $data);
|
||||||
foreach ($data as $statObj) {
|
foreach ($data as $statObj) {
|
||||||
|
|||||||
Reference in New Issue
Block a user