Update and delete multi timesheets and tags (#1240)
This commit is contained in:
@@ -10,12 +10,15 @@
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Tag;
|
||||
use App\Form\MultiUpdate\MultiUpdateTable;
|
||||
use App\Form\MultiUpdate\MultiUpdateTableDTO;
|
||||
use App\Form\TagEditForm;
|
||||
use App\Form\Toolbar\TagToolbarForm;
|
||||
use App\Repository\Query\TagQuery;
|
||||
use App\Repository\TagRepository;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@@ -54,6 +57,7 @@ class TagController extends AbstractController
|
||||
'tags' => $tags,
|
||||
'query' => $query,
|
||||
'toolbarForm' => $form->createView(),
|
||||
'multiUpdateForm' => $this->getMultiUpdateForm($repository)->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -119,6 +123,41 @@ class TagController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/multi-delete", name="tags_multi_delete", methods={"POST"})
|
||||
* @Security("is_granted('delete_tag')")
|
||||
*/
|
||||
public function multiDelete(TagRepository $repository, Request $request)
|
||||
{
|
||||
$form = $this->getMultiUpdateForm($repository);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
/** @var MultiUpdateTableDTO $dto */
|
||||
$dto = $form->getData();
|
||||
$repository->multiDelete($dto->getEntities());
|
||||
$this->flashSuccess('action.delete.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.delete.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('tags');
|
||||
}
|
||||
|
||||
protected function getMultiUpdateForm(TagRepository $repository): FormInterface
|
||||
{
|
||||
$dto = new MultiUpdateTableDTO();
|
||||
$dto->addDelete($this->generateUrl('tags_multi_delete'));
|
||||
|
||||
return $this->createForm(MultiUpdateTable::class, $dto, [
|
||||
'action' => $this->generateUrl('tags'),
|
||||
'repository' => $repository,
|
||||
'method' => 'POST',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TagQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
|
||||
@@ -16,6 +16,10 @@ use App\Entity\Timesheet;
|
||||
use App\Event\TimesheetMetaDefinitionEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Export\ServiceExport;
|
||||
use App\Form\MultiUpdate\MultiUpdateTable;
|
||||
use App\Form\MultiUpdate\MultiUpdateTableDTO;
|
||||
use App\Form\MultiUpdate\TimesheetMultiUpdate;
|
||||
use App\Form\MultiUpdate\TimesheetMultiUpdateDTO;
|
||||
use App\Form\TimesheetEditForm;
|
||||
use App\Form\Toolbar\TimesheetToolbarForm;
|
||||
use App\Repository\ActivityRepository;
|
||||
@@ -130,6 +134,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
'page' => $query->getPage(),
|
||||
'query' => $query,
|
||||
'toolbarForm' => $form->createView(),
|
||||
'multiUpdateForm' => $this->getMultiUpdateActionForm()->createView(),
|
||||
'showSummary' => $this->includeSummary(),
|
||||
'showStartEndTime' => $this->canSeeStartEndTime(),
|
||||
'metaColumns' => $this->findMetaColumns($query, $location),
|
||||
@@ -174,6 +179,24 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getTags(TagRepository $tagRepository, $tagNames)
|
||||
{
|
||||
$tags = [];
|
||||
if (!is_array($tagNames)) {
|
||||
$tagNames = explode(',', $tagNames);
|
||||
}
|
||||
foreach ($tagNames as $tagName) {
|
||||
$tag = $tagRepository->findTagByName($tagName);
|
||||
if (!$tag) {
|
||||
$tag = new Tag();
|
||||
$tag->setName($tagName);
|
||||
}
|
||||
$tags[] = $tag;
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
protected function create(Request $request, string $renderTemplate, ProjectRepository $projectRepository, ActivityRepository $activityRepository, TagRepository $tagRepository): Response
|
||||
{
|
||||
$entry = new Timesheet();
|
||||
@@ -190,13 +213,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
}
|
||||
|
||||
if ($request->query->get('tags')) {
|
||||
$tagNames = explode(',', $request->query->get('tags'));
|
||||
foreach ($tagNames as $tagName) {
|
||||
$tag = $tagRepository->findTagByName($tagName);
|
||||
if (!$tag) {
|
||||
$tag = new Tag();
|
||||
$tag->setName($tagName);
|
||||
}
|
||||
foreach ($this->getTags($tagRepository, $request->query->get('tags')) as $tag) {
|
||||
$entry->addTag($tag);
|
||||
}
|
||||
}
|
||||
@@ -267,11 +284,142 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
return $exporter->render($entries, $query);
|
||||
}
|
||||
|
||||
protected function multiUpdate(Request $request, string $renderTemplate)
|
||||
{
|
||||
$dto = new TimesheetMultiUpdateDTO();
|
||||
|
||||
// initial request from the listing posts a different form
|
||||
$form = $this->getMultiUpdateActionForm();
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$dto->setEntities($form->getData()->getEntities());
|
||||
}
|
||||
|
||||
$form = $this->getMultiUpdateForm($dto);
|
||||
$form->handleRequest($request);
|
||||
|
||||
// remove all, which are not allowed to be edited
|
||||
$timesheets = [];
|
||||
/** @var Timesheet $timesheet */
|
||||
foreach ($dto->getEntities() as $timesheet) {
|
||||
if (!$this->isGranted('edit', $timesheet)) {
|
||||
continue;
|
||||
}
|
||||
$timesheets[] = $timesheet;
|
||||
}
|
||||
$dto->setEntities($timesheets);
|
||||
|
||||
if (count($dto->getEntities()) === 0) {
|
||||
return $this->redirectToRoute($this->getTimesheetRoute());
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var Timesheet $timesheet */
|
||||
$execute = false;
|
||||
foreach ($dto->getEntities() as $timesheet) {
|
||||
if ($dto->isReplaceTags()) {
|
||||
foreach ($timesheet->getTags() as $tag) {
|
||||
$timesheet->removeTag($tag);
|
||||
}
|
||||
$execute = true;
|
||||
}
|
||||
foreach ($dto->getTags() as $tag) {
|
||||
$timesheet->addTag($tag);
|
||||
$execute = true;
|
||||
}
|
||||
if (null !== $dto->getActivity()) {
|
||||
$timesheet->setActivity($dto->getActivity());
|
||||
$execute = true;
|
||||
}
|
||||
if (null !== $dto->getProject()) {
|
||||
$timesheet->setProject($dto->getProject());
|
||||
$execute = true;
|
||||
}
|
||||
if (null !== $dto->getUser()) {
|
||||
$timesheet->setUser($dto->getUser());
|
||||
$execute = true;
|
||||
}
|
||||
if (null !== $dto->isExported()) {
|
||||
$timesheet->setExported($dto->isExported());
|
||||
$execute = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($execute) {
|
||||
try {
|
||||
$this->repository->saveMultiple($dto->getEntities());
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
return $this->redirectToRoute($this->getTimesheetRoute());
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.update.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render($renderTemplate, [
|
||||
'form' => $form->createView(),
|
||||
'dto' => $dto,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function multiDelete(Request $request)
|
||||
{
|
||||
$form = $this->getMultiUpdateActionForm();
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$dto = $form->getData();
|
||||
$timesheets = [];
|
||||
/** @var Timesheet $timesheet */
|
||||
foreach ($dto->getEntities() as $timesheet) {
|
||||
if (!$this->isGranted('delete', $timesheet)) {
|
||||
continue;
|
||||
}
|
||||
$timesheets[] = $timesheet;
|
||||
}
|
||||
$dto->setEntities($timesheets);
|
||||
|
||||
try {
|
||||
$this->repository->deleteMultiple($dto->getEntities());
|
||||
$this->flashSuccess('action.delete.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.delete.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute($this->getTimesheetRoute());
|
||||
}
|
||||
|
||||
protected function prepareQuery(TimesheetQuery $query)
|
||||
{
|
||||
$query->setUser($this->getUser());
|
||||
}
|
||||
|
||||
protected function getMultiUpdateForm(TimesheetMultiUpdateDTO $multiUpdate): FormInterface
|
||||
{
|
||||
return $this->createForm(TimesheetMultiUpdate::class, $multiUpdate, [
|
||||
'action' => $this->generateUrl($this->getMultiUpdateRoute(), []),
|
||||
'method' => 'POST',
|
||||
'include_exported' => $this->isGranted($this->getPermissionEditExport()),
|
||||
'include_user' => $this->includeUserInForms(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getMultiUpdateActionForm(): FormInterface
|
||||
{
|
||||
$dto = new MultiUpdateTableDTO();
|
||||
|
||||
$dto->addUpdate($this->generateUrl($this->getMultiUpdateRoute()));
|
||||
$dto->addDelete($this->generateUrl($this->getMultiDeleteRoute()));
|
||||
|
||||
return $this->createForm(MultiUpdateTable::class, $dto, [
|
||||
'action' => $this->generateUrl($this->getTimesheetRoute()),
|
||||
'repository' => $this->getRepository(),
|
||||
'method' => 'POST',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getCreateForm(Timesheet $entry, TrackingModeInterface $mode): FormInterface
|
||||
{
|
||||
return $this->createForm($this->getCreateFormClassName(), $entry, [
|
||||
@@ -325,6 +473,11 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getPermissionEditExport(): string
|
||||
{
|
||||
return 'edit_export_own_timesheet';
|
||||
}
|
||||
|
||||
protected function getCreateFormClassName(): string
|
||||
{
|
||||
return TimesheetEditForm::class;
|
||||
@@ -360,6 +513,16 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
return 'timesheet_create';
|
||||
}
|
||||
|
||||
protected function getMultiUpdateRoute(): string
|
||||
{
|
||||
return 'timesheet_multi_update';
|
||||
}
|
||||
|
||||
protected function getMultiDeleteRoute(): string
|
||||
{
|
||||
return 'timesheet_multi_delete';
|
||||
}
|
||||
|
||||
protected function canSeeStartEndTime(): bool
|
||||
{
|
||||
return $this->getTrackingMode()->canSeeBeginAndEndTimes();
|
||||
|
||||
@@ -65,6 +65,24 @@ class TimesheetController extends TimesheetAbstractController
|
||||
return $this->edit($entry, $request, 'timesheet/edit.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/multi-update", name="timesheet_multi_update", methods={"POST"})
|
||||
* @Security("is_granted('edit_own_timesheet')")
|
||||
*/
|
||||
public function multiUpdateAction(Request $request)
|
||||
{
|
||||
return $this->multiUpdate($request, 'timesheet/multi-update.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/multi-delete", name="timesheet_multi_delete", methods={"POST"})
|
||||
* @Security("is_granted('delete_own_timesheet')")
|
||||
*/
|
||||
public function multiDeleteAction(Request $request)
|
||||
{
|
||||
return $this->multiDelete($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/create", name="timesheet_create", methods={"GET", "POST"})
|
||||
* @Security("is_granted('create_own_timesheet')")
|
||||
|
||||
@@ -80,11 +80,34 @@ class TimesheetTeamController extends TimesheetAbstractController
|
||||
return $this->create($request, 'timesheet-team/edit.html.twig', $projectRepository, $activityRepository, $tagRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/multi-update", name="admin_timesheet_multi_update", methods={"POST"})
|
||||
* @Security("is_granted('edit_other_timesheet')")
|
||||
*/
|
||||
public function multiUpdateAction(Request $request)
|
||||
{
|
||||
return $this->multiUpdate($request, 'timesheet-team/multi-update.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/multi-delete", name="admin_timesheet_multi_delete", methods={"POST"})
|
||||
* @Security("is_granted('delete_other_timesheet')")
|
||||
*/
|
||||
public function multiDeleteAction(Request $request)
|
||||
{
|
||||
return $this->multiDelete($request);
|
||||
}
|
||||
|
||||
protected function prepareQuery(TimesheetQuery $query)
|
||||
{
|
||||
$query->setCurrentUser($this->getUser());
|
||||
}
|
||||
|
||||
protected function getPermissionEditExport(): string
|
||||
{
|
||||
return 'edit_export_other_timesheet';
|
||||
}
|
||||
|
||||
protected function getCreateFormClassName(): string
|
||||
{
|
||||
return TimesheetAdminEditForm::class;
|
||||
@@ -115,6 +138,16 @@ class TimesheetTeamController extends TimesheetAbstractController
|
||||
return 'admin_timesheet_create';
|
||||
}
|
||||
|
||||
protected function getMultiUpdateRoute(): string
|
||||
{
|
||||
return 'admin_timesheet_multi_update';
|
||||
}
|
||||
|
||||
protected function getMultiDeleteRoute(): string
|
||||
{
|
||||
return 'admin_timesheet_multi_delete';
|
||||
}
|
||||
|
||||
protected function canSeeStartEndTime(): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -63,7 +63,7 @@ final class MenuSubscriber implements EventSubscriberInterface
|
||||
|
||||
if ($auth->isGranted('view_own_timesheet')) {
|
||||
$timesheets = new MenuItemModel('timesheet', 'menu.timesheet', 'timesheet', [], $this->getIcon('timesheet'));
|
||||
$timesheets->setChildRoutes(['timesheet_export', 'timesheet_edit', 'timesheet_create']);
|
||||
$timesheets->setChildRoutes(['timesheet_export', 'timesheet_edit', 'timesheet_create', 'timesheet_multi_update']);
|
||||
$menu->addItem($timesheets);
|
||||
$menu->addItem(
|
||||
new MenuItemModel('calendar', 'calendar.title', 'calendar', [], $this->getIcon('calendar'))
|
||||
@@ -89,7 +89,7 @@ final class MenuSubscriber implements EventSubscriberInterface
|
||||
|
||||
if ($auth->isGranted('view_other_timesheet')) {
|
||||
$timesheets = new MenuItemModel('timesheet_admin', 'menu.admin_timesheet', 'admin_timesheet', [], $this->getIcon('timesheet-team'));
|
||||
$timesheets->setChildRoutes(['admin_timesheet_export', 'admin_timesheet_edit', 'admin_timesheet_create']);
|
||||
$timesheets->setChildRoutes(['admin_timesheet_export', 'admin_timesheet_edit', 'admin_timesheet_create', 'admin_timesheet_multi_update']);
|
||||
$menu->addChild($timesheets);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,10 +64,12 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
$view->vars['attr'] = [];
|
||||
}
|
||||
|
||||
$view->vars['attr'] = array_merge(
|
||||
$view->vars['attr'],
|
||||
['class' => 'selectpicker', 'data-live-search' => true, 'data-width' => '100%']
|
||||
);
|
||||
$extendedOptions = ['class' => 'selectpicker', 'data-width' => '100%'];
|
||||
if (!$options['search']) {
|
||||
$extendedOptions['data-minimum-results-for-search'] = 'Infinity';
|
||||
}
|
||||
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], $extendedOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,5 +80,9 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
$resolver->setDefined(['selectpicker']);
|
||||
$resolver->setAllowedTypes('selectpicker', 'boolean');
|
||||
$resolver->setDefault('selectpicker', true);
|
||||
|
||||
$resolver->setDefined(['search']);
|
||||
$resolver->setAllowedTypes('search', 'boolean');
|
||||
$resolver->setDefault('search', true);
|
||||
}
|
||||
}
|
||||
|
||||
76
src/Form/MultiUpdate/MultiUpdateTable.php
Normal file
76
src/Form/MultiUpdate/MultiUpdateTable.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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\MultiUpdate;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class MultiUpdateTable extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
/** @var EntityRepository $repository */
|
||||
$repository = $options['repository'];
|
||||
/** @var MultiUpdateTableDTO $dto */
|
||||
$dto = $options['data'];
|
||||
|
||||
$builder->add('entities', HiddenType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->get('entities')->addModelTransformer(
|
||||
new CallbackTransformer(
|
||||
function ($ids) {
|
||||
return implode(',', $ids);
|
||||
},
|
||||
function ($ids) use ($repository) {
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $repository->matching((new Criteria())->where(Criteria::expr()->in('id', explode(',', $ids))));
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
$builder->add('action', ChoiceType::class, [
|
||||
'mapped' => false,
|
||||
'required' => false,
|
||||
'choices' => $dto->getActions(),
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefined(['repository']);
|
||||
$resolver->setAllowedTypes('repository', EntityRepository::class);
|
||||
$resolver->setRequired('repository');
|
||||
|
||||
$resolver->setDefaults([
|
||||
'data_class' => MultiUpdateTableDTO::class,
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'csrf_token_id' => 'entities_multiupdate',
|
||||
]);
|
||||
}
|
||||
}
|
||||
86
src/Form/MultiUpdate/MultiUpdateTableDTO.php
Normal file
86
src/Form/MultiUpdate/MultiUpdateTableDTO.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\MultiUpdate;
|
||||
|
||||
class MultiUpdateTableDTO
|
||||
{
|
||||
/**
|
||||
* @var object[]
|
||||
*/
|
||||
private $entities = [];
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $actions = ['' => ''];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $action = null;
|
||||
|
||||
/**
|
||||
* @return object[]
|
||||
*/
|
||||
public function getEntities(): iterable
|
||||
{
|
||||
return $this->entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object[] $entities
|
||||
* @return MultiUpdateTableDTO
|
||||
*/
|
||||
public function setEntities(iterable $entities): MultiUpdateTableDTO
|
||||
{
|
||||
$this->entities = $entities;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getActions(): array
|
||||
{
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
public function addAction(string $label, string $url): MultiUpdateTableDTO
|
||||
{
|
||||
$this->actions[$label] = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDelete(string $url): MultiUpdateTableDTO
|
||||
{
|
||||
$this->actions['action.delete'] = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addUpdate(string $url): MultiUpdateTableDTO
|
||||
{
|
||||
$this->actions['action.edit'] = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAction(): ?string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function setAction(string $action): MultiUpdateTableDTO
|
||||
{
|
||||
$this->action = $action;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
232
src/Form/MultiUpdate/TimesheetMultiUpdate.php
Normal file
232
src/Form/MultiUpdate/TimesheetMultiUpdate.php
Normal file
@@ -0,0 +1,232 @@
|
||||
<?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\MultiUpdate;
|
||||
|
||||
use App\Form\Type\ActivityType;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\TagsInputType;
|
||||
use App\Form\Type\UserType;
|
||||
use App\Form\Type\YesNoType;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\ActivityFormTypeQuery;
|
||||
use App\Repository\Query\CustomerFormTypeQuery;
|
||||
use App\Repository\Query\ProjectFormTypeQuery;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TimesheetMultiUpdate extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var TimesheetRepository
|
||||
*/
|
||||
private $timesheet;
|
||||
/**
|
||||
* @var CustomerRepository
|
||||
*/
|
||||
private $customers;
|
||||
|
||||
public function __construct(TimesheetRepository $timesheet, CustomerRepository $customer)
|
||||
{
|
||||
$this->timesheet = $timesheet;
|
||||
$this->customers = $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$activity = null;
|
||||
$project = null;
|
||||
$customer = null;
|
||||
$customerCount = $this->customers->countCustomer(true);
|
||||
|
||||
if (isset($options['data'])) {
|
||||
/** @var TimesheetMultiUpdateDTO $entry */
|
||||
$entry = $options['data'];
|
||||
|
||||
$activity = $entry->getActivity();
|
||||
$project = $entry->getProject();
|
||||
$customer = null === $project ? null : $project->getCustomer();
|
||||
|
||||
if (null === $project && null !== $activity) {
|
||||
$project = $activity->getProject();
|
||||
}
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('customer', CustomerType::class, [
|
||||
'query_builder' => function (CustomerRepository $repo) use ($builder, $customer) {
|
||||
$query = new CustomerFormTypeQuery($customer);
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
'data' => $customer ? $customer : '',
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'mapped' => false,
|
||||
'project_enabled' => true,
|
||||
])
|
||||
;
|
||||
|
||||
$projectOptions = [];
|
||||
|
||||
if ($customerCount < 2) {
|
||||
$projectOptions['group_by'] = null;
|
||||
}
|
||||
|
||||
$builder
|
||||
->add(
|
||||
'project',
|
||||
ProjectType::class,
|
||||
array_merge($projectOptions, [
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'activity_enabled' => true,
|
||||
'query_builder' => function (ProjectRepository $repo) use ($builder, $project, $customer) {
|
||||
$query = new ProjectFormTypeQuery($project, $customer);
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
// replaces the project select after submission, to make sure only projects for the selected customer are displayed
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($builder, $project, $customer) {
|
||||
$data = $event->getData();
|
||||
$customer = isset($data['customer']) && !empty($data['customer']) ? $data['customer'] : null;
|
||||
$project = isset($data['project']) && !empty($data['project']) ? $data['project'] : $project;
|
||||
|
||||
$event->getForm()->add('project', ProjectType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'activity_enabled' => true,
|
||||
'group_by' => null,
|
||||
'query_builder' => function (ProjectRepository $repo) use ($builder, $project, $customer) {
|
||||
$query = new ProjectFormTypeQuery($project, $customer);
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
$builder
|
||||
->add('activity', ActivityType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'query_builder' => function (ActivityRepository $repo) use ($activity, $project) {
|
||||
return $repo->getQueryBuilderForFormType(new ActivityFormTypeQuery($activity, $project));
|
||||
},
|
||||
])
|
||||
;
|
||||
|
||||
// replaces the activity select after submission, to make sure only activities for the selected project are displayed
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($activity) {
|
||||
$data = $event->getData();
|
||||
if (!isset($data['project']) || empty($data['project'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getForm()->add('activity', ActivityType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'query_builder' => function (ActivityRepository $repo) use ($data, $activity) {
|
||||
return $repo->getQueryBuilderForFormType(new ActivityFormTypeQuery($activity, $data['project']));
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
$builder->add('replaceTags', ChoiceType::class, [
|
||||
'label' => false,
|
||||
'required' => true,
|
||||
'expanded' => true,
|
||||
'choices' => [
|
||||
'label.replaceTags' => true,
|
||||
'label.appendTags' => false,
|
||||
]
|
||||
]);
|
||||
|
||||
$builder->add('tags', TagsInputType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
if ($options['include_user']) {
|
||||
$builder->add('user', UserType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($options['include_exported']) {
|
||||
$builder->add('exported', YesNoType::class, [
|
||||
'label' => 'label.exported'
|
||||
]);
|
||||
}
|
||||
|
||||
$builder->add('entities', HiddenType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->get('entities')->addModelTransformer(
|
||||
new CallbackTransformer(
|
||||
function ($timesheets) {
|
||||
$ids = [];
|
||||
/** @var \App\Entity\Timesheet $timesheet */
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$ids[] = $timesheet->getId();
|
||||
}
|
||||
|
||||
return implode(',', $ids);
|
||||
},
|
||||
function ($ids) {
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->timesheet->matching((new Criteria())->where(Criteria::expr()->in('id', explode(',', $ids))));
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => TimesheetMultiUpdateDTO::class,
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'csrf_token_id' => 'timesheet_multiupdate',
|
||||
'include_user' => false,
|
||||
'include_exported' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
139
src/Form/MultiUpdate/TimesheetMultiUpdateDTO.php
Normal file
139
src/Form/MultiUpdate/TimesheetMultiUpdateDTO.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?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\MultiUpdate;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Tag;
|
||||
use App\Entity\User;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @App\Validator\Constraints\TimesheetMultiUpdate
|
||||
*/
|
||||
class TimesheetMultiUpdateDTO extends MultiUpdateTableDTO
|
||||
{
|
||||
/**
|
||||
* @var Tag[]|ArrayCollection|iterable
|
||||
*/
|
||||
private $tags = [];
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $replaceTags = false;
|
||||
/**
|
||||
* @var Customer|null
|
||||
*/
|
||||
private $customer;
|
||||
/**
|
||||
* @var Project|null
|
||||
*/
|
||||
private $project;
|
||||
/**
|
||||
* @var Activity|null
|
||||
*/
|
||||
private $activity;
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $exported = null;
|
||||
|
||||
public function getCustomer(): ?Customer
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
public function setCustomer(Customer $customer): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setProject(Project $project): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getActivity(): ?Activity
|
||||
{
|
||||
return $this->activity;
|
||||
}
|
||||
|
||||
public function setActivity(Activity $activity): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->activity = $activity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tag[]|ArrayCollection|iterable
|
||||
*/
|
||||
public function getTags(): iterable
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function setTags(iterable $tags): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->tags = $tags;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(User $user): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isExported(): ?bool
|
||||
{
|
||||
return $this->exported;
|
||||
}
|
||||
|
||||
public function setExported(bool $exported): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->exported = $exported;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isReplaceTags(): bool
|
||||
{
|
||||
return $this->replaceTags;
|
||||
}
|
||||
|
||||
public function setReplaceTags(bool $replaceTags): TimesheetMultiUpdateDTO
|
||||
{
|
||||
$this->replaceTags = $replaceTags;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'label' => $label,
|
||||
'search' => false
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -110,6 +111,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
{
|
||||
$builder->add('pageSize', PageSizeType::class, [
|
||||
'required' => false,
|
||||
'search' => false
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -218,6 +220,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
// TODO add a system setting to control if tags can be created on the fly
|
||||
protected function addTagSelectField(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('tags', TagsSelectType::class, [
|
||||
@@ -237,6 +240,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
'label' => 'label.entryState',
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'entryState.all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.running' => TimesheetQuery::STATE_RUNNING,
|
||||
@@ -251,6 +255,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
'label' => 'label.exported',
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'entryState.all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.exported' => TimesheetQuery::STATE_EXPORTED,
|
||||
|
||||
@@ -48,6 +48,14 @@ final class UserIdLoader implements LoaderInterface
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('PARTIAL u.{id}', 'preferences')
|
||||
->from(User::class, 'u')
|
||||
->leftJoin('u.preferences', 'preferences')
|
||||
->andWhere($qb->expr()->in('u.id', $ids))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
$teamIds = [];
|
||||
foreach ($users as $user) {
|
||||
foreach ($user->getTeams() as $team) {
|
||||
|
||||
@@ -28,23 +28,14 @@ class UserQuery extends VisibilityQuery
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRole()
|
||||
public function getRole(): ?string
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $role
|
||||
* @return UserQuery
|
||||
*/
|
||||
public function setRole($role)
|
||||
public function setRole(?string $role): UserQuery
|
||||
{
|
||||
if (null === $role || false !== strpos($role, 'ROLE_')) {
|
||||
$this->role = $role;
|
||||
}
|
||||
$this->role = $role;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -159,4 +159,25 @@ class TagRepository extends EntityRepository
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag[] $tags
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function multiDelete(iterable $tags): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$em->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($tags as $tag) {
|
||||
$em->remove($tag);
|
||||
}
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,27 @@ class TimesheetRepository extends EntityRepository
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteMultiple(iterable $timesheets): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$em->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$em->remove($timesheet);
|
||||
}
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $timesheet
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
@@ -79,6 +100,27 @@ class TimesheetRepository extends EntityRepository
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function saveMultiple(array $timesheets): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$em->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$em->persist($timesheet);
|
||||
}
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $entry
|
||||
* @return bool
|
||||
|
||||
43
src/Validator/Constraints/TimesheetMultiUpdate.php
Normal file
43
src/Validator/Constraints/TimesheetMultiUpdate.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\Validator\Constraints;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
|
||||
*/
|
||||
class TimesheetMultiUpdate extends Constraint
|
||||
{
|
||||
public const MISSING_ACTIVITY_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d84';
|
||||
public const MISSING_PROJECT_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d85';
|
||||
public const ACTIVITY_PROJECT_MISMATCH_ERROR = 'xy5hffg-dsfef3-426a-83d7-1f2d33hs5d86';
|
||||
public const DISABLED_ACTIVITY_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d87';
|
||||
public const DISABLED_PROJECT_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d88';
|
||||
public const DISABLED_CUSTOMER_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d89';
|
||||
|
||||
protected static $errorNames = [
|
||||
self::MISSING_ACTIVITY_ERROR => 'A timesheet must have an activity.',
|
||||
self::MISSING_PROJECT_ERROR => 'A timesheet must have a project.',
|
||||
self::ACTIVITY_PROJECT_MISMATCH_ERROR => 'Project mismatch: chosen project does not match the activity project.',
|
||||
self::DISABLED_ACTIVITY_ERROR => 'Cannot start a disabled activity.',
|
||||
self::DISABLED_PROJECT_ERROR => 'Cannot start a disabled project.',
|
||||
self::DISABLED_CUSTOMER_ERROR => 'Cannot start a disabled customer.',
|
||||
];
|
||||
|
||||
public $message = 'This form has invalid settings.';
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
122
src/Validator/Constraints/TimesheetMultiUpdateValidator.php
Normal file
122
src/Validator/Constraints/TimesheetMultiUpdateValidator.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?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\Configuration\TimesheetConfiguration;
|
||||
use App\Form\MultiUpdate\TimesheetMultiUpdateDTO;
|
||||
use App\Validator\Constraints\TimesheetMultiUpdate as TimesheetConstraint;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
class TimesheetMultiUpdateValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
protected $auth;
|
||||
/**
|
||||
* @var TimesheetConfiguration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @param AuthorizationCheckerInterface $auth
|
||||
* @param TimesheetConfiguration $configuration
|
||||
*/
|
||||
public function __construct(AuthorizationCheckerInterface $auth, TimesheetConfiguration $configuration)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetMultiUpdateDTO|mixed $value
|
||||
* @param Constraint $constraint
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!($constraint instanceof TimesheetConstraint)) {
|
||||
throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\TimesheetMultiUpdate');
|
||||
}
|
||||
|
||||
if (!is_object($value) || !($value instanceof TimesheetMultiUpdateDTO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validateActivityAndProject($value, $this->context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetMultiUpdateDTO $dto
|
||||
* @param ExecutionContextInterface $context
|
||||
*/
|
||||
protected function validateActivityAndProject(TimesheetMultiUpdateDTO $dto, ExecutionContextInterface $context)
|
||||
{
|
||||
$activity = $dto->getActivity();
|
||||
$project = $dto->getProject();
|
||||
|
||||
// non global activity without project
|
||||
if (null !== $activity && null !== $activity->getProject() && null === $project) {
|
||||
$context->buildViolation('Missing project')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::MISSING_PROJECT_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
// only project was chosen
|
||||
if (null === $activity && null !== $project) {
|
||||
$context->buildViolation('Missing activity')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::MISSING_ACTIVITY_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null !== $activity) {
|
||||
if (null !== $activity->getProject() && $activity->getProject() !== $project) {
|
||||
$context->buildViolation('Project mismatch, project specific activity and timesheet project are different.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::ACTIVITY_PROJECT_MISMATCH_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (!$activity->isVisible()) {
|
||||
$context->buildViolation('Cannot assign a disabled activity.')
|
||||
->atPath('activity')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::DISABLED_ACTIVITY_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $project) {
|
||||
if (!$project->isVisible()) {
|
||||
$context->buildViolation('Cannot assign a disabled project.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::DISABLED_PROJECT_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (!$project->getCustomer()->isVisible()) {
|
||||
$context->buildViolation('Cannot assign a disabled customer.')
|
||||
->atPath('customer')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::DISABLED_CUSTOMER_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user