Weekly reporting view (#1892)
This commit is contained in:
@@ -42,4 +42,8 @@ class Constants
|
||||
* Application wide default locale.
|
||||
*/
|
||||
public const DEFAULT_LOCALE = 'en';
|
||||
/**
|
||||
* Default color for Customer, Project and Activity entities
|
||||
*/
|
||||
public const DEFAULT_COLOR = '#d2d6de';
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\LanguageFormattings;
|
||||
use App\Entity\User;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use App\Utils\LocaleFormats;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
|
||||
use Symfony\Component\Translation\DataCollectorTranslator;
|
||||
@@ -108,7 +111,22 @@ abstract class AbstractController extends BaseAbstractController implements Serv
|
||||
{
|
||||
return array_merge(parent::getSubscribedServices(), [
|
||||
'translator' => TranslatorInterface::class,
|
||||
'logger' => LoggerInterface::class
|
||||
'logger' => LoggerInterface::class,
|
||||
LanguageFormattings::class => LanguageFormattings::class,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getDateTimeFactory(?User $user = null): DateTimeFactory
|
||||
{
|
||||
if (null === $user) {
|
||||
$user = $this->getUser();
|
||||
}
|
||||
|
||||
return new DateTimeFactory(new \DateTimeZone($user->getTimezone()));
|
||||
}
|
||||
|
||||
protected function getLocaleFormats(string $locale): LocaleFormats
|
||||
{
|
||||
return new LocaleFormats($this->container->get(LanguageFormattings::class), $locale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,15 @@ use App\Reporting\MonthByUser;
|
||||
use App\Reporting\MonthByUserForm;
|
||||
use App\Reporting\MonthlyUserList;
|
||||
use App\Reporting\MonthlyUserListForm;
|
||||
use App\Reporting\WeekByUser;
|
||||
use App\Reporting\WeekByUserForm;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Timesheet\UserDateTimeFactory;
|
||||
use Exception;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
@@ -39,46 +42,79 @@ final class ReportingController extends AbstractController
|
||||
* @var UserRepository
|
||||
*/
|
||||
private $userRepository;
|
||||
/**
|
||||
* @var UserDateTimeFactory
|
||||
*/
|
||||
private $dateTimeFactory;
|
||||
|
||||
public function __construct(TimesheetRepository $timesheetRepository, UserRepository $userRepository, UserDateTimeFactory $dateTimeFactory)
|
||||
public function __construct(TimesheetRepository $timesheetRepository, UserRepository $userRepository)
|
||||
{
|
||||
$this->timesheetRepository = $timesheetRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->dateTimeFactory = $dateTimeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/", name="reporting", methods={"GET"})
|
||||
* @Route(path="/month_by_user", name="report_user_month", methods={"GET","POST"})
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function monthByUser(Request $request)
|
||||
public function defaultReport(): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
return $this->redirectToRoute('report_user_week');
|
||||
}
|
||||
|
||||
private function canSelectUser(): bool
|
||||
{
|
||||
if (!$this->isGranted('view_other_timesheet')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentUser = $this->getUser();
|
||||
|
||||
if ($currentUser->canSeeAllData()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($currentUser->hasTeamAssignment()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/month_by_user", name="report_user_month", methods={"GET","POST"})
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
public function monthByUser(Request $request): Response
|
||||
{
|
||||
$currentUser = $this->getUser();
|
||||
$dateTimeFactory = $this->getDateTimeFactory($currentUser);
|
||||
$localeFormats = $this->getLocaleFormats($request->getLocale());
|
||||
$canChangeUser = $this->canSelectUser();
|
||||
|
||||
$values = new MonthByUser();
|
||||
$values->setUser($user);
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
$values->setUser($currentUser);
|
||||
$values->setDate($dateTimeFactory->getStartOfMonth());
|
||||
|
||||
$form = $this->createForm(MonthByUserForm::class, $values, [
|
||||
'include_user' => $this->isGranted('view_other_timesheet') && $user->hasTeamAssignment(),
|
||||
'include_user' => $canChangeUser,
|
||||
'timezone' => $dateTimeFactory->getTimezone()->getName(),
|
||||
'start_date' => $values->getDate(),
|
||||
'format' => $localeFormats->getDateTypeFormat(),
|
||||
]);
|
||||
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
if ($values->getUser() === null) {
|
||||
$values->setUser($user);
|
||||
$values->setUser($currentUser);
|
||||
}
|
||||
|
||||
if ($user !== $values->getUser() && !$this->isGranted('view_other_timesheet')) {
|
||||
if ($currentUser !== $values->getUser() && !$canChangeUser) {
|
||||
throw new AccessDeniedException('User is not allowed to see other users timesheet');
|
||||
}
|
||||
|
||||
if ($values->getDate() === null) {
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
$values->setDate($dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
$start = $values->getDate();
|
||||
@@ -110,12 +146,82 @@ final class ReportingController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/monthly_users_list", name="report_monthly_users", methods={"GET","POST"})
|
||||
* @Security("is_granted('view_other_timesheet')")
|
||||
* @Route(path="/week_by_user", name="report_user_week", methods={"GET","POST"})
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
public function montlyhUsersList(Request $request)
|
||||
public function weekByUser(Request $request): Response
|
||||
{
|
||||
$currentUser = $this->getUser();
|
||||
$dateTimeFactory = $this->getDateTimeFactory($currentUser);
|
||||
$localeFormats = $this->getLocaleFormats($request->getLocale());
|
||||
$canChangeUser = $this->canSelectUser();
|
||||
|
||||
$values = new WeekByUser();
|
||||
$values->setUser($currentUser);
|
||||
$values->setDate($dateTimeFactory->getStartOfWeek());
|
||||
|
||||
$form = $this->createForm(WeekByUserForm::class, $values, [
|
||||
'include_user' => $canChangeUser,
|
||||
'timezone' => $dateTimeFactory->getTimezone()->getName(),
|
||||
'start_date' => $values->getDate(),
|
||||
'format' => $localeFormats->getDateTypeFormat(),
|
||||
]);
|
||||
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
if ($values->getUser() === null) {
|
||||
$values->setUser($currentUser);
|
||||
}
|
||||
|
||||
if ($currentUser !== $values->getUser() && !$canChangeUser) {
|
||||
throw new AccessDeniedException('User is not allowed to see other users timesheet');
|
||||
}
|
||||
|
||||
if ($values->getDate() === null) {
|
||||
$values->setDate($dateTimeFactory->getStartOfWeek());
|
||||
}
|
||||
|
||||
$start = $dateTimeFactory->getStartOfWeek($values->getDate());
|
||||
$end = $dateTimeFactory->getEndOfWeek($values->getDate());
|
||||
|
||||
$selectedUser = $values->getUser();
|
||||
|
||||
$previous = clone $start;
|
||||
$previous->modify('-1 week');
|
||||
|
||||
$next = clone $start;
|
||||
$next->modify('+1 week');
|
||||
|
||||
$data = $this->timesheetRepository->getDailyStats($selectedUser, $start, $end);
|
||||
$rows = $this->prepareMonthlyData($data);
|
||||
|
||||
return $this->render('reporting/week_by_user.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'days' => $data,
|
||||
'rows' => $rows,
|
||||
'user' => $selectedUser,
|
||||
'current' => $start,
|
||||
'next' => $next,
|
||||
'previous' => $previous,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/monthly_users_list", name="report_monthly_users", methods={"GET","POST"})
|
||||
* @Security("is_granted('view_other_timesheet')")
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
public function monthlyUsersList(Request $request): Response
|
||||
{
|
||||
$currentUser = $this->getUser();
|
||||
$dateTimeFactory = $this->getDateTimeFactory();
|
||||
$localeFormats = $this->getLocaleFormats($request->getLocale());
|
||||
|
||||
$query = new UserQuery();
|
||||
$query->setCurrentUser($currentUser);
|
||||
@@ -124,18 +230,22 @@ final class ReportingController extends AbstractController
|
||||
$rows = [];
|
||||
|
||||
$values = new MonthlyUserList();
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
$values->setDate($dateTimeFactory->getStartOfMonth());
|
||||
|
||||
$form = $this->createForm(MonthlyUserListForm::class, $values, []);
|
||||
$form = $this->createForm(MonthlyUserListForm::class, $values, [
|
||||
'timezone' => $dateTimeFactory->getTimezone()->getName(),
|
||||
'start_date' => $values->getDate(),
|
||||
'format' => $localeFormats->getDateTypeFormat(),
|
||||
]);
|
||||
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
$values->setDate($dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
if ($values->getDate() === null) {
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
$values->setDate($dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
$start = $values->getDate();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Constants;
|
||||
use App\Export\Annotation as Exporter;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
@@ -36,6 +37,10 @@ trait ColorTrait
|
||||
*/
|
||||
public function getColor(): ?string
|
||||
{
|
||||
if ($this->color === Constants::DEFAULT_COLOR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Constants;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ColorType;
|
||||
@@ -17,7 +18,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ColorPickerType extends AbstractType implements DataTransformerInterface
|
||||
{
|
||||
public const DEFAULT_COLOR = '#d2d6de';
|
||||
public const DEFAULT_COLOR = Constants::DEFAULT_COLOR;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Timesheet\UserDateTimeFactory;
|
||||
use App\Utils\LocaleSettings;
|
||||
use App\Utils\MomentFormatConverter;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
@@ -25,38 +23,16 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
final class MonthPickerType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var LocaleSettings
|
||||
*/
|
||||
private $localeSettings;
|
||||
|
||||
/**
|
||||
* @var UserDateTimeFactory
|
||||
*/
|
||||
private $dateTime;
|
||||
|
||||
public function __construct(LocaleSettings $localeSettings, UserDateTimeFactory $dateTime)
|
||||
{
|
||||
$this->localeSettings = $localeSettings;
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$pickerFormat = $this->localeSettings->getDatePickerFormat();
|
||||
$dateFormat = $this->localeSettings->getDateTypeFormat();
|
||||
$timezone = $this->dateTime->getTimezone()->getName();
|
||||
|
||||
$resolver->setDefaults([
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'format' => $dateFormat,
|
||||
'format_picker' => $pickerFormat,
|
||||
'model_timezone' => $timezone,
|
||||
'view_timezone' => $timezone,
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'start_date' => new \DateTime(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -66,13 +42,13 @@ final class MonthPickerType extends AbstractType
|
||||
$date = $form->getData();
|
||||
|
||||
if (null === $date) {
|
||||
$date = $this->dateTime->getStartOfMonth();
|
||||
$date = $options['start_date'];
|
||||
}
|
||||
|
||||
$view->vars['month'] = $date;
|
||||
$view->vars['previousMonth'] = (clone $date)->modify('-1 month');
|
||||
$view->vars['nextMonth'] = (clone $date)->modify('+1 month');
|
||||
$view->vars['momentFormat'] = (new MomentFormatConverter())->convert($this->localeSettings->getDateTypeFormat());
|
||||
$view->vars['momentFormat'] = (new MomentFormatConverter())->convert($options['format']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
69
src/Form/Type/WeekPickerType.php
Normal file
69
src/Form/Type/WeekPickerType.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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\Form\Type;
|
||||
|
||||
use App\Utils\MomentFormatConverter;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select a week via picker and select previous and next week.
|
||||
*
|
||||
* Always falls back to the current week if none or an invalid date is given.
|
||||
*/
|
||||
final class WeekPickerType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'start_date' => new \DateTime(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
/** @var \DateTime|null $date */
|
||||
$date = $form->getData();
|
||||
|
||||
if (null === $date) {
|
||||
$date = $options['start_date'];
|
||||
}
|
||||
|
||||
$view->vars['week'] = $date;
|
||||
$view->vars['previousWeek'] = (clone $date)->modify('-1 week');
|
||||
$view->vars['nextWeek'] = (clone $date)->modify('+1 week');
|
||||
$view->vars['momentFormat'] = (new MomentFormatConverter())->convert($options['format']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return DateType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'weekpicker';
|
||||
}
|
||||
}
|
||||
48
src/Reporting/DateByUser.php
Normal file
48
src/Reporting/DateByUser.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\Reporting;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
abstract class DateByUser
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
private $date;
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): self
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTime
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(\DateTime $date): self
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -9,40 +9,6 @@
|
||||
|
||||
namespace App\Reporting;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
final class MonthByUser
|
||||
final class MonthByUser extends DateByUser
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var \DateTime
|
||||
*/
|
||||
private $date;
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): MonthByUser
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTime
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(\DateTime $date): MonthByUser
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Reporting;
|
||||
use App\Form\Type\MonthPickerType;
|
||||
use App\Form\Type\UserType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
@@ -32,7 +33,12 @@ class MonthByUserForm extends AbstractType
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('date', MonthPickerType::class);
|
||||
$builder->add('date', MonthPickerType::class, [
|
||||
'model_timezone' => $options['timezone'],
|
||||
'view_timezone' => $options['timezone'],
|
||||
'start_date' => $options['start_date'],
|
||||
'format' => $options['format'],
|
||||
]);
|
||||
|
||||
if ($options['include_user']) {
|
||||
$builder->add('user', UserType::class, ['width' => false]);
|
||||
@@ -46,6 +52,9 @@ class MonthByUserForm extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => MonthByUser::class,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'start_date' => new \DateTime(),
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'include_user' => false,
|
||||
'csrf_protection' => false,
|
||||
'method' => 'GET',
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace App\Reporting;
|
||||
|
||||
use App\Form\Type\MonthPickerType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
@@ -31,7 +32,12 @@ class MonthlyUserListForm extends AbstractType
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('date', MonthPickerType::class);
|
||||
$builder->add('date', MonthPickerType::class, [
|
||||
'model_timezone' => $options['timezone'],
|
||||
'view_timezone' => $options['timezone'],
|
||||
'start_date' => $options['start_date'],
|
||||
'format' => $options['format'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,6 +47,9 @@ class MonthlyUserListForm extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => MonthlyUserList::class,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'start_date' => new \DateTime(),
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'csrf_protection' => false,
|
||||
'method' => 'GET',
|
||||
]);
|
||||
|
||||
14
src/Reporting/WeekByUser.php
Normal file
14
src/Reporting/WeekByUser.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?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\Reporting;
|
||||
|
||||
final class WeekByUser extends DateByUser
|
||||
{
|
||||
}
|
||||
63
src/Reporting/WeekByUserForm.php
Normal file
63
src/Reporting/WeekByUserForm.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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\Reporting;
|
||||
|
||||
use App\Form\Type\UserType;
|
||||
use App\Form\Type\WeekPickerType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class WeekByUserForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* Simplify cross linking between pages by removing the block prefix.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('date', WeekPickerType::class, [
|
||||
'model_timezone' => $options['timezone'],
|
||||
'view_timezone' => $options['timezone'],
|
||||
'start_date' => $options['start_date'],
|
||||
'format' => $options['format'],
|
||||
]);
|
||||
|
||||
if ($options['include_user']) {
|
||||
$builder->add('user', UserType::class, ['width' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => WeekByUser::class,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'start_date' => new \DateTime(),
|
||||
'format' => DateType::HTML5_FORMAT,
|
||||
'include_user' => false,
|
||||
'csrf_protection' => false,
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -444,7 +444,11 @@ class TimesheetRepository extends EntityRepository
|
||||
|
||||
$results[$dateKey]['rate'] += $rate;
|
||||
$results[$dateKey]['duration'] += $duration;
|
||||
$detailsId = $result->getProject()->getCustomer()->getId() . '_' . $result->getProject()->getId();
|
||||
$detailsId =
|
||||
$result->getProject()->getCustomer()->getId()
|
||||
. '_' . $result->getProject()->getId()
|
||||
. '_' . $result->getActivity()->getId()
|
||||
;
|
||||
if (!isset($results[$dateKey]['details'][$detailsId])) {
|
||||
$results[$dateKey]['details'][$detailsId] = [
|
||||
'project' => $result->getProject(),
|
||||
|
||||
101
src/Timesheet/DateTimeFactory.php
Normal file
101
src/Timesheet/DateTimeFactory.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?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;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
|
||||
class DateTimeFactory
|
||||
{
|
||||
/**
|
||||
* @var DateTimeZone
|
||||
*/
|
||||
private $timezone;
|
||||
|
||||
public function __construct(?DateTimeZone $timezone = null)
|
||||
{
|
||||
if (null === $timezone) {
|
||||
$timezone = new \DateTimeZone(date_default_timezone_get());
|
||||
}
|
||||
$this->setTimezone($timezone);
|
||||
}
|
||||
|
||||
public function setTimezone(DateTimeZone $timezone)
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
}
|
||||
|
||||
public function getTimezone(): DateTimeZone
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
|
||||
public function getStartOfMonth(): DateTime
|
||||
{
|
||||
$date = $this->createDateTime('first day of this month');
|
||||
$date->setTime(0, 0, 0);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
public function getStartOfWeek(?DateTime $date = null): DateTime
|
||||
{
|
||||
if (null === $date) {
|
||||
$date = $this->createDateTime('now');
|
||||
}
|
||||
|
||||
return $this->createWeekDateTime($date->format('Y'), $date->format('W'), 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
public function getEndOfWeek(?DateTime $date = null): DateTime
|
||||
{
|
||||
if (null === $date) {
|
||||
$date = $this->createDateTime('now');
|
||||
}
|
||||
|
||||
return $this->createWeekDateTime($date->format('Y'), $date->format('W'), 7, 23, 59, 59);
|
||||
}
|
||||
|
||||
public function getEndOfMonth(): DateTime
|
||||
{
|
||||
$date = $this->createDateTime('last day of this month');
|
||||
$date->setTime(23, 59, 59);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
private function createWeekDateTime($year, $week, $day, $hour, $minute, $second)
|
||||
{
|
||||
$date = new DateTime('now', $this->getTimezone());
|
||||
$date->setISODate($year, $week, $day);
|
||||
$date->setTime($hour, $minute, $second);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
public function createDateTime(string $datetime = 'now'): DateTime
|
||||
{
|
||||
$date = new DateTime($datetime, $this->getTimezone());
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
* @param null|string $datetime
|
||||
* @return bool|DateTime
|
||||
*/
|
||||
public function createDateTimeFromFormat(string $format, ?string $datetime = 'now')
|
||||
{
|
||||
$date = DateTime::createFromFormat($format, $datetime, $this->getTimezone());
|
||||
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
@@ -11,26 +11,31 @@ namespace App\Timesheet;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Security\CurrentUser;
|
||||
use DateTimeZone;
|
||||
|
||||
class UserDateTimeFactory
|
||||
/**
|
||||
* @internal use DateTimeFactory instead: this one relies on the global context and will be deprecated in the future
|
||||
*/
|
||||
class UserDateTimeFactory extends DateTimeFactory
|
||||
{
|
||||
/**
|
||||
* @var \DateTimeZone
|
||||
*/
|
||||
private $timezone;
|
||||
/**
|
||||
* @var CurrentUser
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $initializedFromUser = false;
|
||||
|
||||
public function __construct(CurrentUser $user)
|
||||
{
|
||||
parent::__construct(null);
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function getTimezone(): \DateTimeZone
|
||||
public function getTimezone(): DateTimeZone
|
||||
{
|
||||
if (null === $this->timezone) {
|
||||
if ($this->initializedFromUser === false) {
|
||||
$timezone = date_default_timezone_get();
|
||||
|
||||
$user = $this->user->getUser();
|
||||
@@ -38,44 +43,12 @@ class UserDateTimeFactory
|
||||
$timezone = $user->getTimezone();
|
||||
}
|
||||
|
||||
$this->timezone = new \DateTimeZone($timezone);
|
||||
$timezone = new DateTimeZone($timezone);
|
||||
|
||||
parent::setTimezone($timezone);
|
||||
$this->initializedFromUser = true;
|
||||
}
|
||||
|
||||
return $this->timezone;
|
||||
}
|
||||
|
||||
public function getStartOfMonth(): \DateTime
|
||||
{
|
||||
$date = $this->createDateTime('first day of this month');
|
||||
$date->setTime(0, 0, 0);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
public function getEndOfMonth(): \DateTime
|
||||
{
|
||||
$date = $this->createDateTime('last day of this month');
|
||||
$date->setTime(23, 59, 59);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
public function createDateTime(string $datetime = 'now'): \DateTime
|
||||
{
|
||||
$date = new \DateTime($datetime, $this->getTimezone());
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
* @param null|string $datetime
|
||||
* @return bool|\DateTime
|
||||
*/
|
||||
public function createDateTimeFromFormat(string $format, ?string $datetime = 'now')
|
||||
{
|
||||
$date = \DateTime::createFromFormat($format, $datetime, $this->getTimezone());
|
||||
|
||||
return $date;
|
||||
return parent::getTimezone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,34 +230,36 @@ class DateExtensions extends AbstractExtension
|
||||
return $date->format($this->timeFormat);
|
||||
}
|
||||
|
||||
public function monthName(\DateTime $dateTime): string
|
||||
/**
|
||||
* @see https://framework.zend.com/manual/1.12/en/zend.date.constants.html#zend.date.constants.selfdefinedformats
|
||||
* @see http://userguide.icu-project.org/formatparse/datetime
|
||||
*
|
||||
* @param DateTime $dateTime
|
||||
* @param string $format
|
||||
* @return string
|
||||
*/
|
||||
private function formatIntl(\DateTime $dateTime, string $format): string
|
||||
{
|
||||
// @see http://userguide.icu-project.org/formatparse/datetime
|
||||
$formatter = new \IntlDateFormatter(
|
||||
$this->locale,
|
||||
\IntlDateFormatter::FULL,
|
||||
\IntlDateFormatter::FULL,
|
||||
$dateTime->getTimezone()->getName(),
|
||||
\IntlDateFormatter::GREGORIAN,
|
||||
'LLLL'
|
||||
$format
|
||||
);
|
||||
|
||||
return $formatter->format($dateTime);
|
||||
}
|
||||
|
||||
public function monthName(\DateTime $dateTime, bool $withYear = false): string
|
||||
{
|
||||
return $this->formatIntl($dateTime, ($withYear ? 'LLLL yyyy' : 'LLLL'));
|
||||
}
|
||||
|
||||
public function dayName(\DateTime $dateTime, bool $short = false): string
|
||||
{
|
||||
// @see http://userguide.icu-project.org/formatparse/datetime
|
||||
$formatter = new \IntlDateFormatter(
|
||||
$this->locale,
|
||||
\IntlDateFormatter::FULL,
|
||||
\IntlDateFormatter::FULL,
|
||||
$dateTime->getTimezone()->getName(),
|
||||
\IntlDateFormatter::GREGORIAN,
|
||||
$short ? 'EE' : 'EEEE'
|
||||
);
|
||||
|
||||
return $formatter->format($dateTime);
|
||||
return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
namespace App\Twig;
|
||||
|
||||
use App\Constants;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\EntityWithMetaFields;
|
||||
use App\Entity\Project;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
@@ -27,6 +31,7 @@ class Extensions extends AbstractExtension
|
||||
return [
|
||||
new TwigFilter('docu_link', [$this, 'documentationLink']),
|
||||
new TwigFilter('multiline_indent', [$this, 'multilineIndent']),
|
||||
new TwigFilter('color', [$this, 'color']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -40,6 +45,34 @@ class Extensions extends AbstractExtension
|
||||
];
|
||||
}
|
||||
|
||||
public function color(EntityWithMetaFields $entity): ?string
|
||||
{
|
||||
if ($entity instanceof Activity) {
|
||||
if (!empty($entity->getColor())) {
|
||||
return $entity->getColor();
|
||||
}
|
||||
|
||||
if (null !== $entity->getProject()) {
|
||||
$entity = $entity->getProject();
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity instanceof Project) {
|
||||
if (!empty($entity->getColor())) {
|
||||
return $entity->getColor();
|
||||
}
|
||||
$entity = $entity->getCustomer();
|
||||
}
|
||||
|
||||
if ($entity instanceof Customer) {
|
||||
if (!empty($entity->getColor())) {
|
||||
return $entity->getColor();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @return null|string
|
||||
|
||||
@@ -54,6 +54,7 @@ final class ReportingExtension extends AbstractExtension
|
||||
$event = new ReportingEvent($user);
|
||||
|
||||
if ($this->security->isGranted('view_reporting')) {
|
||||
$event->addReport(new Report('week_by_user', 'report_user_week', 'report_user_week'));
|
||||
$event->addReport(new Report('month_by_user', 'report_user_month', 'report_user_month'));
|
||||
if ($this->security->isGranted('view_other_timesheet')) {
|
||||
$event->addReport(new Report('monthly_users_list', 'report_monthly_users', 'report_monthly_users'));
|
||||
|
||||
Reference in New Issue
Block a user