diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index f184bf100..511d2126e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -51,9 +51,7 @@ function loadDynamicPicker(element) { }, methods: { addNewEntity(entity) { - console.log('addNewEntity', entity); if (this.multiple) { - console.log('adding multiple'); if (!this.picked.some(el => { return el.type === entity.type && el.id === entity.id; })) { @@ -71,7 +69,6 @@ function loadDynamicPicker(element) { } }, removeEntity(entity) { - console.log('removeEntity', entity); this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id)); input.value = JSON.stringify(this.picked); }, @@ -86,7 +83,6 @@ function loadDynamicPicker(element) { document.addEventListener('show-hide-show', function(e) { - console.log('creation event caught') loadDynamicPicker(e.detail.container) }) @@ -94,17 +90,14 @@ document.addEventListener('show-hide-hide', function(e) { console.log('hiding event caught') e.detail.container.querySelectorAll('[data-module="pick-dynamic"]').forEach((el) => { let uniqId = el.dataset.uniqid; - console.log(uniqId); if (appsOnPage.has(uniqId)) { appsOnPage.get(uniqId).unmount(); - console.log('App has been unmounted') appsOnPage.delete(uniqId); } }) }) document.addEventListener('DOMContentLoaded', function(e) { - console.log('loaded event', e) loadDynamicPicker(document) }) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue index c75001005..e69b26619 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue @@ -66,9 +66,18 @@ export default { translatedListOfTypes() { let trans = []; this.types.forEach(t => { - trans.push(appMessages.fr.pick_entity[t].toLowerCase()); + if (this.$props.multiple) { + trans.push(appMessages.fr.pick_entity[t].toLowerCase()); + } else { + trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase()); + } }) - return appMessages.fr.pick_entity.modal_title + trans.join(', '); + + if (this.$props.multiple) { + return appMessages.fr.pick_entity.modal_title + trans.join(', '); + } else { + return appMessages.fr.pick_entity.modal_title_one + trans.join(', '); + } } }, methods: { @@ -79,15 +88,10 @@ export default { ); this.$refs.addPersons.resetSearch(); // to cast child method modal.showModal = false; - console.log(this.picked) }, removeEntity(entity) { - console.log('remove entity', entity); this.$emit('removeEntity', entity); } }, - mounted() { - console.log(this.picked); - } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/i18n.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/i18n.js index f3ae3a928..cc3f324e8 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/i18n.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/i18n.js @@ -11,6 +11,10 @@ const appMessages = { user: 'Utilisateurs', person: 'Usagers', thirdparty: 'Tiers', + modal_title_one: 'Indiquer un ', + user_one: 'Utilisateur', + thirdparty_one: 'Tiers', + person_one: 'Usager', } } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index 8bb09623f..1da384920 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -217,6 +217,7 @@ {% endblock %} {% block pick_entity_dynamic_widget %} + {{ form_help(form)}}
{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php index 4abf5e93c..1fd54f14c 100644 --- a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php +++ b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php @@ -12,13 +12,18 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Templating\Entity\UserRender; use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; @@ -27,11 +32,18 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\Validator\Constraints\NotIdenticalTo; +use Symfony\Component\Validator\Constraints\NotNull; +use function is_int; class ReassignAccompanyingPeriodController extends AbstractController { private AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository; + private AccompanyingPeriodRepository $courseRepository; + + private EntityManagerInterface $em; + private EngineInterface $engine; private FormFactoryInterface $formFactory; @@ -44,8 +56,17 @@ class ReassignAccompanyingPeriodController extends AbstractController private UserRepository $userRepository; - public function __construct(AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, UserRepository $userRepository, EngineInterface $engine, FormFactoryInterface $formFactory, PaginatorFactory $paginatorFactory, Security $security, UserRender $userRender) - { + public function __construct( + AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository, + UserRepository $userRepository, + AccompanyingPeriodRepository $courseRepository, + EngineInterface $engine, + FormFactoryInterface $formFactory, + PaginatorFactory $paginatorFactory, + Security $security, + UserRender $userRender, + EntityManagerInterface $em + ) { $this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; $this->engine = $engine; $this->formFactory = $formFactory; @@ -53,6 +74,8 @@ class ReassignAccompanyingPeriodController extends AbstractController $this->security = $security; $this->userRepository = $userRepository; $this->userRender = $userRender; + $this->courseRepository = $courseRepository; + $this->em = $em; } /** @@ -68,23 +91,55 @@ class ReassignAccompanyingPeriodController extends AbstractController $form->handleRequest($request); - $total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod( - $form['user']->getData() - ); + $userFrom = $form['user']->getData(); + + $total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod($userFrom); $paginator = $this->paginatorFactory->create($total); $periods = $this->accompanyingPeriodACLAwareRepository ->findByUserOpenedAccompanyingPeriod( - $form['user']->getData(), + $userFrom, ['openingDate' => 'ASC'], $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber() ); + $periodIds = []; + + foreach ($periods as $period) { + $periodIds[] = $period->getId(); + } + + // Create an array of period id's to pass into assignForm hiddenfield + $assignForm = $this->buildReassignForm($periodIds, $userFrom); + + $assignForm->handleRequest($request); + + if ($assignForm->isSubmitted() && $assignForm->isValid()) { + $assignPeriodIds = json_decode($assignForm->get('periods')->getData(), true); + $userTo = $assignForm->get('userTo')->getData(); + $userFrom = $assignForm->get('userFrom')->getData(); + + foreach ($assignPeriodIds as $periodId) { + $period = $this->courseRepository->find($periodId); + + if ($period->getUser() === $userFrom) { + $period->setUser($userTo); + } + } + + $this->em->flush(); + + // redirect to the first page + return $this->redirectToRoute('chill_course_list_reassign', $request->query->all()); + } + return new Response( $this->engine->render('@ChillPerson/AccompanyingPeriod/reassign_list.html.twig', [ + 'assignForm' => $assignForm->createView(), + 'form' => $form->createView(), 'paginator' => $paginator, 'periods' => $periods, - 'form' => $form->createView(), + 'userFrom' => $userFrom, ]) ); } @@ -98,17 +153,63 @@ class ReassignAccompanyingPeriodController extends AbstractController 'method' => 'get', 'csrf_protection' => false, ]); $builder - ->add('user', EntityType::class, [ - 'class' => User::class, - 'choices' => $this->userRepository->findByActive(['username' => 'ASC']), - 'choice_label' => function (User $u) { - return $this->userRender->renderString($u, []); - }, + ->add('user', PickUserDynamicType::class, [ 'multiple' => false, - 'label' => 'User', + 'label' => 'reassign.Current user', 'required' => false, + 'help' => 'reassign.Choose a user and click on "Filter" to apply', ]); return $builder->getForm(); } + + private function buildReassignForm(array $periodIds, ?User $userFrom): FormInterface + { + $defaultData = [ + 'userFrom' => $userFrom, + 'periods' => json_encode($periodIds), + ]; + + $builder = $this->formFactory->createNamedBuilder('reassign', FormType::class, $defaultData); + + if (null !== $userFrom) { + $constraints = [new NotIdenticalTo(['value' => $userFrom])]; + } else { + $constraints = []; + } + + $builder + ->add('periods', HiddenType::class) + ->add('userFrom', HiddenType::class) + ->add('userTo', PickUserDynamicType::class, [ + 'multiple' => false, + 'label' => 'reassign.Next user', + 'required' => true, + 'help' => 'reassign.All periods on this list will be reassigned to this user, excepted the one you manually reassigned before', + 'constraints' => [new NotNull()], + ]); + + $builder->get('userFrom')->addModelTransformer(new CallbackTransformer( + static function (?User $user) { + if (null === $user) { + return ''; + } + + return $user->getId(); + }, + function (?string $id) { + if (null === $id) { + return null; + } + + if (!is_int((int) $id)) { + throw new TransformationFailedException('the user id is not a numeric'); + } + + return $this->userRepository->find((int) $id); + } + )); + + return $builder->getForm(); + } } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php index 4fa9802f8..18b2788f6 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php @@ -21,6 +21,7 @@ use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; +use function array_key_exists; use function count; class LoadRelationships extends Fixture implements DependentFixtureInterface @@ -34,7 +35,7 @@ class LoadRelationships extends Fixture implements DependentFixtureInterface $this->em = $em; } - public function getDependencies() + public function getDependencies(): array { return [ LoadPeople::class, @@ -42,9 +43,11 @@ class LoadRelationships extends Fixture implements DependentFixtureInterface ]; } - public function load(ObjectManager $manager) + public function load(ObjectManager $manager): void { - for ($i = 0; 15 > $i; ++$i) { + $existing = []; + + for ($i = 0; 20 > $i; ++$i) { $user = $this->getRandomUser(); $date = new DateTimeImmutable(); $relationship = (new Relationship()) @@ -57,6 +60,17 @@ class LoadRelationships extends Fixture implements DependentFixtureInterface ->setUpdatedBy($user) ->setCreatedAt($date) ->setUpdatedAt($date); + + // remove the potential duplicates + $set = $relationship->getFromPerson()->getId() < $relationship->getToPerson()->getId() ? + [$relationship->getFromPerson()->getId(), $relationship->getToPerson()->getId()] : + [$relationship->getToPerson()->getId(), $relationship->getFromPerson()->getId()]; + + if (array_key_exists($set[0], $existing) && array_key_exists($set[1], $existing[$set[0]])) { + continue; + } + + $existing[$set[0]][$existing[$set[1]]] = 1; $manager->persist($relationship); } diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 6d2bf6d18..25fc251e5 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -873,6 +873,12 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac AccompanyingPeriodVoter::EDIT, AccompanyingPeriodVoter::DELETE, ], + AccompanyingPeriodVoter::REASSIGN_BULK => [ + AccompanyingPeriodVoter::CONFIDENTIAL_CRUD, + ], + AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL => [ + AccompanyingPeriodVoter::CONFIDENTIAL_CRUD, + ], ], ]); } diff --git a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php index c95c5caf5..722598ef7 100644 --- a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Knp\Menu\MenuItem; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; @@ -64,13 +65,15 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface ]); } - $menu->addChild($this->translator->trans('Accompanying courses of users'), [ - 'route' => 'chill_course_list_reassign', - ]) - ->setExtras([ - 'order' => 12, - 'icons' => ['task'], - ]); + if ($this->authorizationChecker->isGranted(AccompanyingPeriodVoter::REASSIGN_BULK, null)) { + $menu->addChild($this->translator->trans('reassign.Bulk reassign'), [ + 'route' => 'chill_course_list_reassign', + ]) + ->setExtras([ + 'order' => 40, + 'icons' => [], + ]); + } } public static function getMenuIds(): array diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig index 87166d990..63e25efeb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig @@ -4,17 +4,19 @@ {% block js %} {{ encore_entry_script_tags('mod_set_referrer') }} + {{ encore_entry_script_tags('mod_pickentity_type') }} {% endblock %} {% block css %} {{ encore_entry_link_tags('mod_set_referrer') }} + {{ encore_entry_link_tags('mod_pickentity_type') }} {% endblock %} {% macro period_meta(period) %} {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', period) %}