create timesheet for multiple users (#1716)
This commit is contained in:
@@ -15,6 +15,7 @@ Perform EACH version specific task between your version and the new one, otherwi
|
||||
### Developer
|
||||
|
||||
- **BC break**: interface method signature `HtmlToPdfConverter::convertToPdf` changed
|
||||
- **BC break**: the macros `badge` and `label` do not apply the `|trans` filter any more
|
||||
|
||||
## [1.9](https://github.com/kevinpapst/kimai2/releases/tag/1.9)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
/**
|
||||
* @var TimesheetService
|
||||
*/
|
||||
private $service;
|
||||
protected $service;
|
||||
|
||||
public function __construct(
|
||||
UserDateTimeFactory $dateTime,
|
||||
|
||||
@@ -9,14 +9,22 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Tag;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Form\Model\MultiUserTimesheet;
|
||||
use App\Form\TimesheetAdminEditForm;
|
||||
use App\Form\TimesheetMultiUserEditForm;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Repository\TagRepository;
|
||||
use App\Timesheet\TrackingMode\TrackingModeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@@ -59,7 +67,7 @@ class TimesheetTeamController extends TimesheetAbstractController
|
||||
*
|
||||
* @param Timesheet $entry
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
* @return RedirectResponse|Response
|
||||
*/
|
||||
public function editAction(Timesheet $entry, Request $request)
|
||||
{
|
||||
@@ -73,13 +81,89 @@ class TimesheetTeamController extends TimesheetAbstractController
|
||||
* @param Request $request
|
||||
* @param ProjectRepository $projectRepository
|
||||
* @param ActivityRepository $activityRepository
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
* @return RedirectResponse|Response
|
||||
*/
|
||||
public function createAction(Request $request, ProjectRepository $projectRepository, ActivityRepository $activityRepository, TagRepository $tagRepository)
|
||||
{
|
||||
return $this->create($request, 'timesheet-team/edit.html.twig', $projectRepository, $activityRepository, $tagRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/create_mu", name="admin_timesheet_create_multiuser", methods={"GET", "POST"})
|
||||
* @Security("is_granted('create_other_timesheet')")
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse|Response
|
||||
*/
|
||||
public function createForMultiUserAction(Request $request)
|
||||
{
|
||||
$entry = new MultiUserTimesheet();
|
||||
$entry->setUser($this->getUser());
|
||||
$this->service->prepareNewTimesheet($entry, $request);
|
||||
|
||||
$mode = $this->getTrackingMode();
|
||||
$createForm = $this->getMultiUserCreateForm($entry, $mode);
|
||||
$createForm->handleRequest($request);
|
||||
|
||||
if ($createForm->isSubmitted() && $createForm->isValid()) {
|
||||
try {
|
||||
/** @var ArrayCollection $users */
|
||||
$users = $createForm->get('users')->getData();
|
||||
/** @var ArrayCollection $teams */
|
||||
$teams = $createForm->get('teams')->getData();
|
||||
|
||||
$allUsers = $users->toArray();
|
||||
foreach ($teams as $team) {
|
||||
$allUsers = array_merge($allUsers, $team->getUsers()->toArray());
|
||||
}
|
||||
$allUsers = array_unique($allUsers);
|
||||
|
||||
/** @var Tag[] $tags */
|
||||
$tags = [];
|
||||
/** @var Tag $tag */
|
||||
foreach ($entry->getTags() as $tag) {
|
||||
$tag->removeTimesheet($entry);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
|
||||
foreach ($allUsers as $user) {
|
||||
$newTimesheet = $entry->createCopy();
|
||||
$newTimesheet->setUser($user);
|
||||
foreach ($tags as $tag) {
|
||||
$newTimesheet->addTag($tag);
|
||||
}
|
||||
$this->service->prepareNewTimesheet($newTimesheet, $request);
|
||||
$this->service->saveNewTimesheet($newTimesheet);
|
||||
}
|
||||
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
return $this->redirectToRoute($this->getTimesheetRoute());
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.update.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('timesheet-team/edit.html.twig', [
|
||||
'timesheet' => $entry,
|
||||
'form' => $createForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getMultiUserCreateForm(MultiUserTimesheet $entry, TrackingModeInterface $mode): FormInterface
|
||||
{
|
||||
return $this->createForm(TimesheetMultiUserEditForm::class, $entry, [
|
||||
'action' => $this->generateUrl('admin_timesheet_create_multiuser'),
|
||||
'include_rate' => $this->isGranted('edit_rate', $entry),
|
||||
'include_exported' => $this->isGranted('edit_export', $entry),
|
||||
'include_user' => $this->includeUserInForms('create'),
|
||||
'allow_begin_datetime' => $mode->canEditBegin(),
|
||||
'allow_end_datetime' => $mode->canEditEnd(),
|
||||
'allow_duration' => $mode->canEditDuration(),
|
||||
'customer' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/multi-update", name="admin_timesheet_multi_update", methods={"POST"})
|
||||
* @Security("is_granted('edit_other_timesheet')")
|
||||
|
||||
@@ -557,6 +557,27 @@ class Timesheet implements EntityWithMetaFields, ExportItemInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createCopy(?Timesheet $timesheet = null): Timesheet
|
||||
{
|
||||
if (null === $timesheet) {
|
||||
$timesheet = new Timesheet();
|
||||
}
|
||||
|
||||
$values = get_object_vars($this);
|
||||
foreach ($values as $k => $v) {
|
||||
$timesheet->$k = $v;
|
||||
}
|
||||
|
||||
$timesheet->meta = new ArrayCollection();
|
||||
|
||||
/** @var TimesheetMeta $meta */
|
||||
foreach ($this->meta as $meta) {
|
||||
$timesheet->setMetaField(clone $meta);
|
||||
}
|
||||
|
||||
return $timesheet;
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->id) {
|
||||
|
||||
86
src/Form/Model/MultiUserTimesheet.php
Normal file
86
src/Form/Model/MultiUserTimesheet.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?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\Model;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* @App\Validator\Constraints\TimesheetMultiUser
|
||||
*/
|
||||
final class MultiUserTimesheet extends Timesheet
|
||||
{
|
||||
/**
|
||||
* @var Collection<User>
|
||||
*/
|
||||
private $users;
|
||||
/**
|
||||
* @var Collection<Team>
|
||||
*/
|
||||
private $teams;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->users = new ArrayCollection();
|
||||
$this->teams = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<User>
|
||||
*/
|
||||
public function getUsers(): Collection
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
public function addUser(User $user)
|
||||
{
|
||||
$this->users->add($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUser(User $user)
|
||||
{
|
||||
if ($this->users->contains($user)) {
|
||||
$this->users->remove($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<Team>
|
||||
*/
|
||||
public function getTeams(): Collection
|
||||
{
|
||||
return $this->teams;
|
||||
}
|
||||
|
||||
public function addTeam(Team $team)
|
||||
{
|
||||
$this->teams->add($team);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTeam(Team $team)
|
||||
{
|
||||
if ($this->teams->contains($team)) {
|
||||
$this->teams->remove($team);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
43
src/Form/TimesheetMultiUserEditForm.php
Normal file
43
src/Form/TimesheetMultiUserEditForm.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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;
|
||||
|
||||
use App\Form\Type\TeamMemberType;
|
||||
use App\Form\Type\TeamType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TimesheetMultiUserEditForm extends TimesheetAdminEditForm
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$options['allow_begin_datetime'] = true;
|
||||
$options['allow_end_datetime'] = true;
|
||||
$options['allow_duration'] = false;
|
||||
$options['include_user'] = false;
|
||||
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->add('users', TeamMemberType::class, [
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->add('teams', TeamType::class, [
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ class PageSizeType extends AbstractType
|
||||
500 => 500
|
||||
],
|
||||
'placeholder' => null,
|
||||
'choice_translation_domain' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
68
src/Form/Type/TeamMemberType.php
Normal file
68
src/Form/Type/TeamMemberType.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Select a user that
|
||||
*/
|
||||
class TeamMemberType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => User::class,
|
||||
'label' => 'label.user',
|
||||
'choice_label' => function (User $user) {
|
||||
return $user->getDisplayName();
|
||||
},
|
||||
]);
|
||||
|
||||
$resolver->setDefault('query_builder', function (Options $options) {
|
||||
return function (UserRepository $repo) use ($options) {
|
||||
$qb = $repo->createQueryBuilder('u');
|
||||
$qb
|
||||
->andWhere($qb->expr()->eq('u.enabled', ':enabled'))
|
||||
->setParameter('enabled', true, \PDO::PARAM_BOOL)
|
||||
->orderBy('u.username', 'ASC');
|
||||
|
||||
/** @var User $user */
|
||||
$user = $options['user'];
|
||||
|
||||
if (null !== $user && !$user->getTeams()->isEmpty() && !$user->isSuperAdmin() && !$user->isAdmin()) {
|
||||
$qb
|
||||
->leftJoin('u.teams', 'teams')
|
||||
->leftJoin('teams.users', 'users')
|
||||
->andWhere($qb->expr()->isMemberOf(':teams', 'u.teams'))
|
||||
->setParameter('teams', $user->getTeams());
|
||||
}
|
||||
|
||||
return $qb;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class UserType extends AbstractType
|
||||
'choice_label' => function (User $user) {
|
||||
return $user->getDisplayName();
|
||||
},
|
||||
'choice_translation_domain' => false,
|
||||
]);
|
||||
|
||||
$resolver->setDefault('query_builder', function (Options $options) {
|
||||
|
||||
33
src/Validator/Constraints/TimesheetMultiUser.php
Normal file
33
src/Validator/Constraints/TimesheetMultiUser.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
|
||||
*/
|
||||
class TimesheetMultiUser extends Constraint
|
||||
{
|
||||
public const MISSING_USER_OR_TEAM = 'ts-multi-user-01';
|
||||
|
||||
protected static $errorNames = [
|
||||
self::MISSING_USER_OR_TEAM => 'You must select at least one user or team.',
|
||||
];
|
||||
|
||||
public $message = 'This form has invalid settings.';
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
47
src/Validator/Constraints/TimesheetMultiUserValidator.php
Normal file
47
src/Validator/Constraints/TimesheetMultiUserValidator.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Form\Model\MultiUserTimesheet;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
final class TimesheetMultiUserValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @param Timesheet|mixed $value
|
||||
* @param Constraint $constraint
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!($constraint instanceof TimesheetMultiUser)) {
|
||||
throw new UnexpectedTypeException($constraint, TimesheetMultiUser::class);
|
||||
}
|
||||
|
||||
if (!\is_object($value) || !($value instanceof MultiUserTimesheet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value->getTeams()->isEmpty() && $value->getUsers()->isEmpty()) {
|
||||
$this->context->buildViolation('You must select at least one user or team.')
|
||||
->atPath('users')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetMultiUser::MISSING_USER_OR_TEAM)
|
||||
->addViolation();
|
||||
|
||||
$this->context->buildViolation('You must select at least one user or team.')
|
||||
->atPath('teams')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetMultiUser::MISSING_USER_OR_TEAM)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,9 @@
|
||||
{% macro label_boolean(visible) %}
|
||||
{% import _self as macro %}
|
||||
{% if visible %}
|
||||
{{ macro.label('yes', 'success') }}
|
||||
{{ macro.label('yes'|trans, 'success') }}
|
||||
{% else %}
|
||||
{{ macro.label('no', 'default') }}
|
||||
{{ macro.label('no'|trans, 'default') }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
|
||||
{% macro label_role(role) %}
|
||||
{% import _self as macro %}
|
||||
{% set color = 'primary' %}
|
||||
{% if role == 'ROLE_SUPER_ADMIN' %}
|
||||
{{ macro.label(role, 'danger') }}
|
||||
{% set color = 'danger' %}
|
||||
{% elseif role == 'ROLE_ADMIN' %}
|
||||
{{ macro.label(role, 'warning') }}
|
||||
{% set color = 'warning' %}
|
||||
{% elseif role == 'ROLE_TEAMLEAD' %}
|
||||
{{ macro.label(role, 'success') }}
|
||||
{% set color = 'success' %}
|
||||
{% elseif role == 'ROLE_USER' %}
|
||||
{{ macro.label(role, 'gray') }}
|
||||
{% else %}
|
||||
{{ macro.label(role, 'primary') }}
|
||||
{% set color = 'gray' %}
|
||||
{% endif %}
|
||||
{{ macro.label(role|trans, color) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro username(user) %}
|
||||
@@ -155,11 +155,11 @@
|
||||
|
||||
{% macro label(title, type, tooltip) %}
|
||||
{# success, warning, danger, primary #}
|
||||
<span {% if tooltip %}data-toggle="tooltip" data-placement="top" title="{{ tooltip }}" {% endif %}class="label label-{{ type|default('success') }}">{{ title|trans }}</span>
|
||||
<span {% if tooltip %}data-toggle="tooltip" data-placement="top" title="{{ tooltip }}" {% endif %}class="label label-{{ type|default('success') }}">{{ title }}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro badge(title, color) %}
|
||||
<span class="badge" style="background-color:{{ color }}">{{ title|trans }}</span>
|
||||
<span class="badge" style="background-color:{{ color }}">{{ title }}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro alert(type, description, title, icon) %}
|
||||
@@ -345,7 +345,7 @@
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">{{ 'label.toggle_dropdown'|trans }}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
{% for childIcon,childValues in values.children %}
|
||||
<li>{{ macro.action_button(childIcon, childValues, false) }}</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'visibility': '#modal_timesheet_admin'}) %}
|
||||
{% if is_granted('create_other_timesheet') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('admin_timesheet_create'), 'class': 'modal-ajax-form'}}) %}
|
||||
{% set actions = actions|merge({'create': {'children': {'single': {'title': 'create'|trans,'url': path('admin_timesheet_create'), 'class': 'create-ts modal-ajax-form'}, 'multi-user': {'title': 'create-timesheet-multiuser'|trans({}, 'actions'),'url': path('admin_timesheet_create_multiuser'), 'class': 'create-ts-mu modal-ajax-form'}}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
@@ -57,6 +57,27 @@
|
||||
{% if form.tags is defined %}
|
||||
{{ form_row(form.tags) }}
|
||||
{% endif %}
|
||||
{% if form.user is defined %}
|
||||
{{ form_row(form.user) }}
|
||||
{% endif %}
|
||||
{% if form.users is defined or form.teams is defined %}
|
||||
{% set uLength = 12 %}
|
||||
{% if form.users is defined and form.teams is defined %}
|
||||
{% set uLength = 6 %}
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
{% if form.users is defined %}
|
||||
<div class="col-md-{{ uLength }}">
|
||||
{{ form_row(form.users) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.teams is defined %}
|
||||
<div class="col-md-{{ uLength }}">
|
||||
{{ form_row(form.teams) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.fixedRate is defined and form.hourlyRate is defined %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
@@ -47,7 +47,8 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
||||
'toolbar-action exporter-pdf' => $this->createUrl('/team/timesheet/export/pdf'),
|
||||
'toolbar-action exporter-xlsx' => $this->createUrl('/team/timesheet/export/xlsx'),
|
||||
'visibility' => '#',
|
||||
'create modal-ajax-form' => $this->createUrl('/team/timesheet/create'),
|
||||
'create-ts modal-ajax-form' => $this->createUrl('/team/timesheet/create'),
|
||||
'create-ts-mu modal-ajax-form' => $this->createUrl('/team/timesheet/create_mu'),
|
||||
'help' => 'https://www.kimai.org/documentation/timesheet.html'
|
||||
]);
|
||||
}
|
||||
@@ -200,6 +201,65 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
||||
$this->assertNull($timesheet->getFixedRate());
|
||||
}
|
||||
|
||||
public function testCreateForMultipleUsersAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/team/timesheet/create_mu');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=timesheet_multi_user_edit_form]')->form();
|
||||
$client->submit($form, [
|
||||
'timesheet_multi_user_edit_form' => [
|
||||
'description' => 'Testing is more fun!',
|
||||
'project' => 1,
|
||||
'activity' => 1,
|
||||
'teams' => '1',
|
||||
'tags' => 'test,1234,foo-bar',
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/team/timesheet/'));
|
||||
$client->followRedirect();
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
/** @var Timesheet[] $timesheets */
|
||||
$timesheets = $em->getRepository(Timesheet::class)->findAll();
|
||||
$this->assertCount(2, $timesheets);
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
|
||||
$this->assertNull($timesheet->getEnd());
|
||||
$this->assertEquals('Testing is more fun!', $timesheet->getDescription());
|
||||
$this->assertEquals(0, $timesheet->getRate());
|
||||
$this->assertNull($timesheet->getHourlyRate());
|
||||
$this->assertNull($timesheet->getFixedRate());
|
||||
$this->assertEquals(['test', '1234', 'foo-bar'], $timesheet->getTagsAsArray());
|
||||
}
|
||||
}
|
||||
|
||||
public function testCreateForMultipleUsersActionWithoutUserOrTeam()
|
||||
{
|
||||
$data = [
|
||||
'timesheet_multi_user_edit_form' => [
|
||||
'description' => 'Testing is more fun!',
|
||||
'project' => 1,
|
||||
'activity' => 1,
|
||||
// make sure the default validation for timesheets is applied as well
|
||||
'begin' => (new \DateTime())->format('Y-m-d H:i'),
|
||||
'end' => (new \DateTime('-1 hour'))->format('Y-m-d H:i'),
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertFormHasValidationError(
|
||||
User::ROLE_ADMIN,
|
||||
'/team/timesheet/create_mu',
|
||||
'form[name=timesheet_multi_user_edit_form]',
|
||||
$data,
|
||||
['#timesheet_multi_user_edit_form_users', '#timesheet_multi_user_edit_form_teams', '#timesheet_multi_user_edit_form_end']
|
||||
);
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Validator\Constraints;
|
||||
|
||||
use App\Form\Model\MultiUserTimesheet;
|
||||
use App\Validator\Constraints\TimesheetMultiUser;
|
||||
use App\Validator\Constraints\TimesheetMultiUserValidator;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Validator\Constraints\TimesheetMultiUserValidator
|
||||
*/
|
||||
class TimesheetMultiUserValidatorTest extends ConstraintValidatorTestCase
|
||||
{
|
||||
protected function createValidator($isGranted = true)
|
||||
{
|
||||
return new TimesheetMultiUserValidator();
|
||||
}
|
||||
|
||||
public function testConstraintIsInvalid()
|
||||
{
|
||||
$this->expectException(UnexpectedTypeException::class);
|
||||
|
||||
$this->validator->validate('foo', new NotBlank());
|
||||
}
|
||||
|
||||
public function testEmptyTimesheet()
|
||||
{
|
||||
$timesheet = new MultiUserTimesheet();
|
||||
|
||||
$this->validator->validate($timesheet, new TimesheetMultiUser(['message' => 'myMessage']));
|
||||
|
||||
$this->buildViolation('You must select at least one user or team.')
|
||||
->atPath('property.path.users')
|
||||
->setCode(TimesheetMultiUser::MISSING_USER_OR_TEAM)
|
||||
->buildNextViolation('You must select at least one user or team.')
|
||||
->atPath('property.path.teams')
|
||||
->setCode(TimesheetMultiUser::MISSING_USER_OR_TEAM)
|
||||
->assertRaised();
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,10 @@
|
||||
<source>create-timesheet</source>
|
||||
<target>Zeit erfassen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="create-timesheet-multiuser">
|
||||
<source>create-timesheet-multiuser</source>
|
||||
<target>Für mehrere Benutzer</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="permissions">
|
||||
<source>permissions</source>
|
||||
<target>Team Berechtigungen</target>
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
<source>create-timesheet</source>
|
||||
<target>Create timesheet</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="create-timesheet-multiuser">
|
||||
<source>create-timesheet-multiuser</source>
|
||||
<target>For multiple users</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="permissions">
|
||||
<source>permissions</source>
|
||||
<target>Team permissions</target>
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
<source>The begin date cannot be in the future.</source>
|
||||
<target>Das Startdatum darf nicht in der Zukunft liegen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="You must select at least one user or team.">
|
||||
<source>You must select at least one user or team.</source>
|
||||
<target>Sie müssen mindestens einen Benutzer oder ein Team auswählen.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
<source>The begin date cannot be in the future.</source>
|
||||
<target>The begin date cannot be in the future.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="You must select at least one user or team.">
|
||||
<source>You must select at least one user or team.</source>
|
||||
<target>You must select at least one user or team.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
Reference in New Issue
Block a user