diff --git a/src/Constants.php b/src/Constants.php index c6e82c13..6b5da059 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -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'; } diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index f365ea01..d23ec533 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -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); + } } diff --git a/src/Controller/ReportingController.php b/src/Controller/ReportingController.php index 4a80e779..f75206c8 100644 --- a/src/Controller/ReportingController.php +++ b/src/Controller/ReportingController.php @@ -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(); diff --git a/src/Entity/ColorTrait.php b/src/Entity/ColorTrait.php index 2a0f7ecc..c9bfae16 100644 --- a/src/Entity/ColorTrait.php +++ b/src/Entity/ColorTrait.php @@ -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; } diff --git a/src/Form/Type/ColorPickerType.php b/src/Form/Type/ColorPickerType.php index 57296f84..ecfe0de8 100644 --- a/src/Form/Type/ColorPickerType.php +++ b/src/Form/Type/ColorPickerType.php @@ -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} diff --git a/src/Form/Type/MonthPickerType.php b/src/Form/Type/MonthPickerType.php index 35b3a21a..d4311dde 100644 --- a/src/Form/Type/MonthPickerType.php +++ b/src/Form/Type/MonthPickerType.php @@ -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']); } /** diff --git a/src/Form/Type/WeekPickerType.php b/src/Form/Type/WeekPickerType.php new file mode 100644 index 00000000..f2ff139a --- /dev/null +++ b/src/Form/Type/WeekPickerType.php @@ -0,0 +1,69 @@ +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'; + } +} diff --git a/src/Reporting/DateByUser.php b/src/Reporting/DateByUser.php new file mode 100644 index 00000000..41ac9318 --- /dev/null +++ b/src/Reporting/DateByUser.php @@ -0,0 +1,48 @@ +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; + } +} diff --git a/src/Reporting/MonthByUser.php b/src/Reporting/MonthByUser.php index 719bdb0e..978029a4 100644 --- a/src/Reporting/MonthByUser.php +++ b/src/Reporting/MonthByUser.php @@ -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; - } } diff --git a/src/Reporting/MonthByUserForm.php b/src/Reporting/MonthByUserForm.php index d64f3b50..36b384e1 100644 --- a/src/Reporting/MonthByUserForm.php +++ b/src/Reporting/MonthByUserForm.php @@ -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', diff --git a/src/Reporting/MonthlyUserListForm.php b/src/Reporting/MonthlyUserListForm.php index 7fffeae1..b83600b5 100644 --- a/src/Reporting/MonthlyUserListForm.php +++ b/src/Reporting/MonthlyUserListForm.php @@ -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', ]); diff --git a/src/Reporting/WeekByUser.php b/src/Reporting/WeekByUser.php new file mode 100644 index 00000000..b2c3f91e --- /dev/null +++ b/src/Reporting/WeekByUser.php @@ -0,0 +1,14 @@ +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', + ]); + } +} diff --git a/src/Repository/TimesheetRepository.php b/src/Repository/TimesheetRepository.php index 559b7c95..00f762f5 100644 --- a/src/Repository/TimesheetRepository.php +++ b/src/Repository/TimesheetRepository.php @@ -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(), diff --git a/src/Timesheet/DateTimeFactory.php b/src/Timesheet/DateTimeFactory.php new file mode 100644 index 00000000..3c037f73 --- /dev/null +++ b/src/Timesheet/DateTimeFactory.php @@ -0,0 +1,101 @@ +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; + } +} diff --git a/src/Timesheet/UserDateTimeFactory.php b/src/Timesheet/UserDateTimeFactory.php index 021e21d3..e4843be6 100644 --- a/src/Timesheet/UserDateTimeFactory.php +++ b/src/Timesheet/UserDateTimeFactory.php @@ -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(); } } diff --git a/src/Twig/DateExtensions.php b/src/Twig/DateExtensions.php index 7180e4b1..35f75ad8 100644 --- a/src/Twig/DateExtensions.php +++ b/src/Twig/DateExtensions.php @@ -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')); } /** diff --git a/src/Twig/Extensions.php b/src/Twig/Extensions.php index 99cddd05..1fe95fa8 100644 --- a/src/Twig/Extensions.php +++ b/src/Twig/Extensions.php @@ -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 diff --git a/src/Twig/ReportingExtension.php b/src/Twig/ReportingExtension.php index 08ba3428..08a3e27c 100644 --- a/src/Twig/ReportingExtension.php +++ b/src/Twig/ReportingExtension.php @@ -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')); diff --git a/templates/form/kimai-theme.html.twig b/templates/form/kimai-theme.html.twig index 30eaffc5..65e06e46 100644 --- a/templates/form/kimai-theme.html.twig +++ b/templates/form/kimai-theme.html.twig @@ -48,26 +48,30 @@ {% block monthpicker_widget -%}
- + - {{ month|month_name|trans ~ ' ' ~ month|date_format('Y') }} + {{ month|month_name(true) }} - +
- - {% set type = 'hidden' %} - {{ block('form_widget_simple') }} + {{ block('hidden_widget') }} {%- endblock monthpicker_widget %} + +{% block weekpicker_widget -%} +
+ + + + + {{ 'stats.workingTimeWeek'|trans({'%week%': week|date_format('W')}) }} + + + + +
+ {{ block('hidden_widget') }} +{%- endblock weekpicker_widget %} diff --git a/templates/reporting/month_by_user.html.twig b/templates/reporting/month_by_user.html.twig index 1d63271a..04da63f9 100644 --- a/templates/reporting/month_by_user.html.twig +++ b/templates/reporting/month_by_user.html.twig @@ -4,6 +4,13 @@ {% block report %} + {% set hasData = false %} + {% for day in days %} + {% if day.details is not empty %} + {% set hasData = true %} + {% endif %} + {% endfor %} + {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% import "macros/widgets.html.twig" as widgets %} {% block box_before %} @@ -20,10 +27,14 @@ {% endif %} {{ form_widget(form.date) }} {% endblock %} - {% block box_body_class %}user-month-reporting-box no-padding table-responsive{% endblock %} + {% block box_body_class %}user-month-reporting-box table-responsive{% if hasData %} no-padding{% endif %}{% endblock %} {% block box_body %} + {% if not hasData %} + {{ widgets.nothing_found() }} + {% else %} + {% for day in days %} {% endfor %} - {% for project in rows %} + {% for day in project.days %} - {% endfor %} - {% for activity in project.activities %} + {% for day in activity.days %} - {% endfor %} - {% endfor %} {% endfor %} {% set total = 0 %} - {% for day in days %} - + + {% for day in days %} + {% endfor %} -
@@ -31,52 +42,54 @@ {{ day.day|date_format('d.m') }}
{{ widgets.label_project(project.project) }} {{ project.duration|duration }} + {% if day.duration > 0 %} - {{ day.duration|duration }} + {{ day.duration|duration }} {% endif %} {{ project.duration|duration }}
{{ widgets.label_activity(activity.activity) }} {{ activity.duration|duration }} + {% if day.duration > 0 %} {{ day.duration|duration }} {% endif %} {{ activity.duration|duration }}
+ {% set total = total + day.totalDuration %} + {% endfor %} + {{ total|duration }} {% if day.totalDuration > 0 %} {{ day.totalDuration|duration }} - {% set total = total + day.totalDuration %} {% endif %} {{ total|duration }}
+ {% endif %} {% endblock %} {% endembed %} diff --git a/templates/reporting/monthly_user_list.html.twig b/templates/reporting/monthly_user_list.html.twig index 3462e492..b82e641d 100644 --- a/templates/reporting/monthly_user_list.html.twig +++ b/templates/reporting/monthly_user_list.html.twig @@ -15,10 +15,11 @@ {% block box_title %} {{ form_widget(form.date) }} {% endblock %} - {% block box_body_class %}monthly-user-list-reporting-box no-padding table-responsive{% endblock %} + {% block box_body_class %}monthly-user-list-reporting-box table-responsive no-padding{% endblock %} {% block box_body %} + {% for day in days %} {% endfor %} - {% for userDay in rows %} {% set usersMonthDuration = 0 %} @@ -34,15 +34,25 @@ + {% for day in userDay.days %} + {% if day.totalDuration > 0 %} + {% set usersMonthDuration = usersMonthDuration + day.totalDuration %} + {% endif %} + {% endfor %} + {% for day in userDay.days %} {% endfor %} - {% endfor %}
@@ -26,7 +27,6 @@ {{ day|date_format('d.m') }}
{{ widgets.username(userDay.user) }} + {% if usersMonthDuration == 0 %} + - + {% else %} + {{ usersMonthDuration|duration }} + {% endif %} + {% if day.totalDuration > 0 %} {{ day.totalDuration|duration }} - {% set usersMonthDuration = usersMonthDuration + day.totalDuration %} {% endif %} {{ usersMonthDuration|duration }}
diff --git a/templates/reporting/week_by_user.html.twig b/templates/reporting/week_by_user.html.twig new file mode 100644 index 00000000..60599536 --- /dev/null +++ b/templates/reporting/week_by_user.html.twig @@ -0,0 +1,112 @@ +{% extends 'reporting/layout.html.twig' %} + +{% block report_title %}{{ 'report_user_week'|trans({}, 'reporting') }}{% endblock %} + +{% block report %} + + {% set hasData = false %} + {% for day in days %} + {% if day.details is not empty %} + {% set hasData = true %} + {% endif %} + {% endfor %} + + {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} + {% import "macros/widgets.html.twig" as widgets %} + {% block box_before %} + {{ form_start(form, {'action': path('report_user_week'), 'attr': {'class': 'form-inline'}}) }} + {% endblock %} + {% block box_after %} + {{ form_end(form) }} + {% endblock %} + {% block box_title %} + {% if form.user is defined %} + {{ form_widget(form.user) }} + {% else %} + {{ widgets.username(user) }} + {% endif %} + {{ form_widget(form.date) }} + {% endblock %} + {% block box_body_class %}user-week-reporting-box table-responsive{% if hasData %} no-padding{% endif %}{% endblock %} + {% block box_body %} + {% if not hasData %} + {{ widgets.nothing_found() }} + {% else %} + + + + + {% for day in days %} + + {% endfor %} + + {% for project in rows %} + + + + {% for day in project.days %} + + {% endfor %} + + {% for activity in project.activities %} + + + + {% for day in activity.days %} + + {% endfor %} + + {% endfor %} + {% endfor %} + {% set total = 0 %} + + {% for day in days %} + {% set total = total + day.totalDuration %} + {% endfor %} + + + {% for day in days %} + + {% endfor %} + +
+ {{ day.day|day_name(true) }}
+ {{ day.day|date_format('d.m') }} +
+ {{ widgets.label_project(project.project) }} + {{ project.duration|duration }} + {% if day.duration > 0 %} + {{ day.duration|duration }} + {% endif %} +
+ {{ widgets.label_activity(activity.activity) }} + {{ activity.duration|duration }} + {% if day.duration > 0 %} + {{ day.duration|duration }} + {% endif %} +
{{ total|duration }} + {% if day.totalDuration > 0 %} + {{ day.totalDuration|duration }} + {% endif %} +
+ {% endif %} + {% endblock %} + {% endembed %} + +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/tests/Controller/ReportingControllerTest.php b/tests/Controller/ReportingControllerTest.php index ed5a11ee..d064744c 100644 --- a/tests/Controller/ReportingControllerTest.php +++ b/tests/Controller/ReportingControllerTest.php @@ -10,6 +10,7 @@ namespace App\Tests\Controller; use App\Entity\User; +use App\Tests\DataFixtures\TimesheetFixtures; /** * @group integration @@ -21,6 +22,16 @@ class ReportingControllerTest extends ControllerBaseTest $this->assertUrlIsSecured('/reporting'); } + public function testWeekByUserIsSecure() + { + $this->assertUrlIsSecured('/reporting/week_by_user'); + } + + public function testMonthByUserIsSecure() + { + $this->assertUrlIsSecured('/reporting/month_by_user'); + } + public function testMonthlyListIsSecure() { $this->assertUrlIsSecured('/reporting/monthly_users_list'); @@ -31,17 +42,53 @@ class ReportingControllerTest extends ControllerBaseTest $this->assertUrlIsSecuredForRole(User::ROLE_USER, '/reporting/monthly_users_list'); } - public function testDefaultUsersMonthReport() + protected function importReportingFixture(string $role) + { + $fixture = new TimesheetFixtures(); + $fixture->setAmount(50); + $fixture->setAmountRunning(10); + $fixture->setUser($this->getUserByRole($role)); + $fixture->setStartDate(new \DateTime()); + $this->importFixture($fixture); + } + + public function testRedirectForDefaultReportUrl() { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); - $this->assertAccessIsGranted($client, '/reporting/'); + $this->importReportingFixture(User::ROLE_USER); + $this->request($client, '/reporting/'); + $this->assertIsRedirect($client, $this->createUrl('/reporting/week_by_user')); + $client->followRedirect(); + self::assertStringContainsString('
getResponse()->getContent()); + $option = $client->getCrawler()->filterXPath("//select[@id='user']/option[@selected]"); + self::assertEquals(4, $option->attr('value')); + } + + public function testUserMonthReport() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $this->importReportingFixture(User::ROLE_USER); + $this->assertAccessIsGranted($client, '/reporting/month_by_user?user=4&date=12999119191'); self::assertStringContainsString('
count()); } public function testMonthlyUsersReport() { $client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD); + $this->importReportingFixture(User::ROLE_TEAMLEAD); $this->assertAccessIsGranted($client, '/reporting/monthly_users_list'); self::assertStringContainsString('
count()); } } diff --git a/tests/Entity/ActivityTest.php b/tests/Entity/ActivityTest.php index 10dbf8fc..288ece92 100644 --- a/tests/Entity/ActivityTest.php +++ b/tests/Entity/ActivityTest.php @@ -9,6 +9,7 @@ namespace App\Tests\Entity; +use App\Constants; use App\Entity\Activity; use App\Entity\ActivityMeta; use App\Entity\Project; @@ -58,6 +59,9 @@ class ActivityTest extends TestCase $this->assertInstanceOf(Activity::class, $sut->setColor('#fffccc')); $this->assertEquals('#fffccc', $sut->getColor()); + $this->assertInstanceOf(Activity::class, $sut->setColor(Constants::DEFAULT_COLOR)); + $this->assertNull($sut->getColor()); + $this->assertInstanceOf(Activity::class, $sut->setBudget(12345.67)); $this->assertEquals(12345.67, $sut->getBudget()); diff --git a/tests/Entity/CustomerTest.php b/tests/Entity/CustomerTest.php index 559588b6..72a89672 100644 --- a/tests/Entity/CustomerTest.php +++ b/tests/Entity/CustomerTest.php @@ -9,6 +9,7 @@ namespace App\Tests\Entity; +use App\Constants; use App\Entity\Customer; use App\Entity\CustomerMeta; use App\Entity\Team; @@ -72,6 +73,9 @@ class CustomerTest extends TestCase self::assertInstanceOf(Customer::class, $sut->setColor('#fffccc')); self::assertEquals('#fffccc', $sut->getColor()); + self::assertInstanceOf(Customer::class, $sut->setColor(Constants::DEFAULT_COLOR)); + self::assertNull($sut->getColor()); + self::assertInstanceOf(Customer::class, $sut->setCompany('test company')); self::assertEquals('test company', $sut->getCompany()); diff --git a/tests/Entity/ProjectTest.php b/tests/Entity/ProjectTest.php index d1298521..49964554 100644 --- a/tests/Entity/ProjectTest.php +++ b/tests/Entity/ProjectTest.php @@ -9,6 +9,7 @@ namespace App\Tests\Entity; +use App\Constants; use App\Entity\Customer; use App\Entity\Project; use App\Entity\ProjectMeta; @@ -82,6 +83,9 @@ class ProjectTest extends TestCase self::assertInstanceOf(Project::class, $sut->setColor('#fffccc')); self::assertEquals('#fffccc', $sut->getColor()); + self::assertInstanceOf(Project::class, $sut->setColor(Constants::DEFAULT_COLOR)); + self::assertNull($sut->getColor()); + self::assertInstanceOf(Project::class, $sut->setVisible(false)); self::assertFalse($sut->isVisible()); diff --git a/tests/Reporting/AbstractDateByUserTest.php b/tests/Reporting/AbstractDateByUserTest.php new file mode 100644 index 00000000..46cfac3a --- /dev/null +++ b/tests/Reporting/AbstractDateByUserTest.php @@ -0,0 +1,43 @@ +createSut(); + self::assertNull($sut->getDate()); + self::assertNull($sut->getUser()); + } + + public function testSetter() + { + $date = new \DateTime('2019-05-27'); + $user = new User(); + $user->setAlias('sdfsdfdsdf'); + + $sut = $this->createSut(); + self::assertInstanceOf(DateByUser::class, $sut->setDate($date)); + self::assertInstanceOf(DateByUser::class, $sut->setUser($user)); + + self::assertSame($date, $sut->getDate()); + self::assertSame($user, $sut->getUser()); + } +} diff --git a/tests/Reporting/MonthByUserTest.php b/tests/Reporting/MonthByUserTest.php new file mode 100644 index 00000000..651d16e1 --- /dev/null +++ b/tests/Reporting/MonthByUserTest.php @@ -0,0 +1,25 @@ +createDateTimeFactory(self::TEST_TIMEZONE); + $this->assertEquals(self::TEST_TIMEZONE, $sut->getTimezone()->getName()); + } + + public function testGetTimezoneWithFallbackTimezone() + { + $sut = $this->createDateTimeFactory(); + $this->assertEquals(date_default_timezone_get(), $sut->getTimezone()->getName()); + } + + public function testGetStartOfMonth() + { + $expected = new DateTime('now', new DateTimeZone(self::TEST_TIMEZONE)); + + $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $dateTime = $sut->getStartOfMonth(); + $this->assertEquals(0, $dateTime->format('H')); + $this->assertEquals(0, $dateTime->format('i')); + $this->assertEquals(0, $dateTime->format('s')); + $this->assertEquals(1, $dateTime->format('d')); + $this->assertEquals($expected->format('m'), $dateTime->format('m')); + $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + } + + public function testGetEndOfMonth() + { + $expected = new DateTime('last day of this month', new DateTimeZone(self::TEST_TIMEZONE)); + + $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $dateTime = $sut->getEndOfMonth(); + $this->assertEquals(23, $dateTime->format('H')); + $this->assertEquals(59, $dateTime->format('i')); + $this->assertEquals(59, $dateTime->format('s')); + $this->assertEquals($expected->format('d'), $dateTime->format('d')); + $this->assertEquals($expected->format('m'), $dateTime->format('m')); + $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + } + + public function testGetStartOfWeek() + { + $expected = new DateTime('2018-07-26 16:47:31', new DateTimeZone(self::TEST_TIMEZONE)); + + $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $dateTime = $sut->getStartOfWeek($expected); + + $this->assertEquals(0, $dateTime->format('H')); + $this->assertEquals(0, $dateTime->format('i')); + $this->assertEquals(0, $dateTime->format('s')); + $this->assertEquals(23, $dateTime->format('d')); + $this->assertEquals(1, $dateTime->format('N')); + $this->assertEquals('Monday', $dateTime->format('l')); + $this->assertEquals($expected->format('m'), $dateTime->format('m')); + $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + + $expected = new DateTime('now', new DateTimeZone(self::TEST_TIMEZONE)); + $dateTime = $sut->getStartOfWeek(); + + $this->assertEquals(0, $dateTime->format('H')); + $this->assertEquals(0, $dateTime->format('i')); + $this->assertEquals(0, $dateTime->format('s')); + $this->assertEquals(1, $dateTime->format('N')); + $this->assertEquals('Monday', $dateTime->format('l')); + $this->assertEquals($expected->format('m'), $dateTime->format('m')); + $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + } + + public function testGetEndOfWeek() + { + $expected = new DateTime('2018-07-26 16:47:31', new DateTimeZone(self::TEST_TIMEZONE)); + + $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $dateTime = $sut->getEndOfWeek($expected); + + $this->assertEquals(23, $dateTime->format('H')); + $this->assertEquals(59, $dateTime->format('i')); + $this->assertEquals(59, $dateTime->format('s')); + $this->assertEquals(29, $dateTime->format('d')); + $this->assertEquals(7, $dateTime->format('N')); + $this->assertEquals('Sunday', $dateTime->format('l')); + $this->assertEquals($expected->format('m'), $dateTime->format('m')); + $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + + $expected = new DateTime('now', new DateTimeZone(self::TEST_TIMEZONE)); + $dateTime = $sut->getEndOfWeek(); + + $this->assertEquals(23, $dateTime->format('H')); + $this->assertEquals(59, $dateTime->format('i')); + $this->assertEquals(59, $dateTime->format('s')); + $this->assertEquals(7, $dateTime->format('N')); + $this->assertEquals('Sunday', $dateTime->format('l')); + $this->assertEquals($expected->format('m'), $dateTime->format('m')); + $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + } + + public function testCreateDateTime() + { + $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $dateTime = $sut->createDateTime('2015-07-24 13:45:21'); + $this->assertEquals(13, $dateTime->format('H')); + $this->assertEquals(45, $dateTime->format('i')); + $this->assertEquals(21, $dateTime->format('s')); + $this->assertEquals('24', $dateTime->format('d')); + $this->assertEquals('07', $dateTime->format('m')); + $this->assertEquals('2015', $dateTime->format('Y')); + $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); + } + + public function testCreateDateTimeWithDefaultValue() + { + $expected = new DateTime('now', new DateTimeZone(self::TEST_TIMEZONE)); + + $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $dateTime = $sut->createDateTime(); + $difference = $expected->getTimestamp() - $dateTime->getTimestamp(); + // poor test, but there shouldn't be more than 2 seconds between the creation of two DateTime objects + $this->assertTrue(2 >= $difference); + } +} diff --git a/tests/Timesheet/UserDateTimeFactoryTest.php b/tests/Timesheet/UserDateTimeFactoryTest.php index ad37f44f..678db403 100644 --- a/tests/Timesheet/UserDateTimeFactoryTest.php +++ b/tests/Timesheet/UserDateTimeFactoryTest.php @@ -14,80 +14,27 @@ use App\Timesheet\UserDateTimeFactory; use PHPUnit\Framework\TestCase; /** + * @covers \App\Timesheet\DateTimeFactory * @covers \App\Timesheet\UserDateTimeFactory */ class UserDateTimeFactoryTest extends TestCase { - public const TEST_TIMEZONE = 'Europe/London'; + public const TEST_TIMEZONE = 'Africa/Asmara'; - protected function createDateTimeFactory(?string $timezone = null): UserDateTimeFactory + protected function createUserDateTimeFactory(?string $timezone = null): UserDateTimeFactory { return (new UserDateTimeFactoryFactory($this))->create($timezone); } public function testGetTimezone() { - $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); + $sut = $this->createUserDateTimeFactory(self::TEST_TIMEZONE); $this->assertEquals(self::TEST_TIMEZONE, $sut->getTimezone()->getName()); } public function testGetTimezoneWithFallbackTimezone() { - $sut = $this->createDateTimeFactory(); + $sut = $this->createUserDateTimeFactory(); $this->assertEquals(date_default_timezone_get(), $sut->getTimezone()->getName()); } - - public function testGetStartOfMonth() - { - $expected = new \DateTime('now', new \DateTimeZone(self::TEST_TIMEZONE)); - - $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); - $dateTime = $sut->getStartOfMonth(); - $this->assertEquals(0, $dateTime->format('H')); - $this->assertEquals(0, $dateTime->format('i')); - $this->assertEquals(0, $dateTime->format('s')); - $this->assertEquals(1, $dateTime->format('d')); - $this->assertEquals($expected->format('m'), $dateTime->format('m')); - $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); - $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); - } - - public function testGetEndOfMonth() - { - $expected = new \DateTime('last day of this month', new \DateTimeZone(self::TEST_TIMEZONE)); - - $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); - $dateTime = $sut->getEndOfMonth(); - $this->assertEquals(23, $dateTime->format('H')); - $this->assertEquals(59, $dateTime->format('i')); - $this->assertEquals(59, $dateTime->format('s')); - $this->assertEquals($expected->format('d'), $dateTime->format('d')); - $this->assertEquals($expected->format('m'), $dateTime->format('m')); - $this->assertEquals($expected->format('Y'), $dateTime->format('Y')); - $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); - } - - public function testCreateDateTime() - { - $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); - $dateTime = $sut->createDateTime('2015-07-24 13:45:21'); - $this->assertEquals(13, $dateTime->format('H')); - $this->assertEquals(45, $dateTime->format('i')); - $this->assertEquals(21, $dateTime->format('s')); - $this->assertEquals('24', $dateTime->format('d')); - $this->assertEquals('07', $dateTime->format('m')); - $this->assertEquals('2015', $dateTime->format('Y')); - $this->assertEquals(self::TEST_TIMEZONE, $dateTime->getTimezone()->getName()); - } - - public function testCreateDateTimeWithDefaultValue() - { - $expected = new \DateTime('now', new \DateTimeZone(self::TEST_TIMEZONE)); - - $sut = $this->createDateTimeFactory(self::TEST_TIMEZONE); - $dateTime = $sut->createDateTime(); - $difference = $expected->getTimestamp() - $dateTime->getTimestamp(); - // poor test, but there shouldn't be more than 2 seconds between the creation of two DateTime objects - $this->assertTrue(2 >= $difference); - } } diff --git a/tests/Twig/DateExtensionsTest.php b/tests/Twig/DateExtensionsTest.php index f20b81f0..9c1b743f 100644 --- a/tests/Twig/DateExtensionsTest.php +++ b/tests/Twig/DateExtensionsTest.php @@ -139,20 +139,27 @@ class DateExtensionsTest extends TestCase /** * @dataProvider getMonthNameTestData */ - public function testMonthName(string $locale, string $date, string $expectedName) + public function testMonthName(string $locale, string $date, string $expectedName, bool $withYear = false) { $sut = $this->getSut($locale, []); - self::assertEquals($expectedName, $sut->monthName(new \DateTime($date))); + self::assertEquals($expectedName, $sut->monthName(new \DateTime($date), $withYear)); } public function getMonthNameTestData() { return [ - ['de', '2020-07-09 23:59:59', 'Juli'], - ['en', '2020-07-09 23:59:59', 'July'], - ['de', 'January 2016', 'Januar'], - ['en', 'January 2016', 'January'], - ['en', '2016-12-23', 'December'], + ['de', '2020-07-09 23:59:59', 'Juli', false], + ['en', '2020-07-09 23:59:59', 'July', false], + ['de', 'January 2016', 'Januar', false], + ['en', 'January 2016', 'January', false], + ['en', '2016-12-23', 'December', false], + ['ru', '2016-12-23', 'декабрь', false], + ['de', '2020-07-09 23:59:59', 'Juli 2020', true], + ['en', '2020-07-09 23:59:59', 'July 2020', true], + ['de', 'January 2016', 'Januar 2016', true], + ['en', 'January 2016', 'January 2016', true], + ['en', '2015-12-23', 'December 2015', true], + ['ru', '2015-12-23', 'декабрь 2015', true], ]; } diff --git a/tests/Twig/ExtensionsTest.php b/tests/Twig/ExtensionsTest.php index a58bf4cd..bbe8fe51 100644 --- a/tests/Twig/ExtensionsTest.php +++ b/tests/Twig/ExtensionsTest.php @@ -9,6 +9,9 @@ namespace App\Tests\Twig; +use App\Entity\Activity; +use App\Entity\Customer; +use App\Entity\Project; use App\Entity\User; use App\Twig\Extensions; use PHPUnit\Framework\TestCase; @@ -27,7 +30,7 @@ class ExtensionsTest extends TestCase public function testGetFilters() { - $filters = ['docu_link', 'multiline_indent']; + $filters = ['docu_link', 'multiline_indent', 'color']; $sut = $this->getSut(); $twigFilters = $sut->getFilters(); $this->assertCount(\count($filters), $twigFilters); @@ -116,4 +119,39 @@ sdfsdf' . PHP_EOL . "\n" . $sut = $this->getSut(); self::assertEquals(implode("\n", $expected), $sut->multilineIndent($string, $indent)); } + + public function testColor() + { + $sut = $this->getSut(); + + $globalActivity = new Activity(); + self::assertNull($sut->color($globalActivity)); + + $globalActivity->setColor('#000001'); + self::assertEquals('#000001', $sut->color($globalActivity)); + + $customer = new Customer(); + self::assertNull($sut->color($customer)); + + $customer->setColor('#000004'); + self::assertEquals('#000004', $sut->color($customer)); + + $project = new Project(); + self::assertNull($sut->color($project)); + + $project->setCustomer($customer); + self::assertEquals('#000004', $sut->color($project)); + + $project->setColor('#000003'); + self::assertEquals('#000003', $sut->color($project)); + + $activity = new Activity(); + self::assertNull($sut->color($activity)); + + $activity->setProject($project); + self::assertEquals('#000003', $sut->color($activity)); + + $activity->setColor('#000002'); + self::assertEquals('#000002', $sut->color($activity)); + } } diff --git a/tests/Twig/ReportingExtensionTest.php b/tests/Twig/ReportingExtensionTest.php index ee155be7..6216d5fe 100644 --- a/tests/Twig/ReportingExtensionTest.php +++ b/tests/Twig/ReportingExtensionTest.php @@ -57,6 +57,6 @@ class ReportingExtensionTest extends TestCase $sut = $this->getSut(true); $reports = $sut->getAvailableReports(new User()); self::assertIsArray($reports); - self::assertCount(2, $reports); + self::assertCount(3, $reports); } } diff --git a/translations/messages.nl.xlf b/translations/messages.nl.xlf index 420cf2f7..49fe1c7d 100644 --- a/translations/messages.nl.xlf +++ b/translations/messages.nl.xlf @@ -648,6 +648,10 @@ + + stats.workingTimeWeek + Kalenderweek %week% + stats.durationToday Prestaties vandaag diff --git a/translations/reporting.de.xlf b/translations/reporting.de.xlf index f17fd1d2..0d11fa3c 100644 --- a/translations/reporting.de.xlf +++ b/translations/reporting.de.xlf @@ -6,6 +6,10 @@ reporting.title Reporting + + report_user_week + Wochenansicht für einen Benutzer + report_user_month Monatsansicht für einen Benutzer diff --git a/translations/reporting.en.xlf b/translations/reporting.en.xlf index 2bf56be2..af7e1c36 100644 --- a/translations/reporting.en.xlf +++ b/translations/reporting.en.xlf @@ -6,6 +6,10 @@ reporting.title Reporting + + report_user_week + Weekly view for one user + report_user_month Monthly view for one user diff --git a/translations/reporting.nl.xlf b/translations/reporting.nl.xlf new file mode 100644 index 00000000..0178ef23 --- /dev/null +++ b/translations/reporting.nl.xlf @@ -0,0 +1,23 @@ + + + + + + reporting.title + Rapporten + + + report_user_week + Weekoverzicht voor een user + + + report_user_month + Maandoverzicht voor een user + + + report_monthly_users + Maandoverzicht voor alle users + + + +