diff --git a/CHANGELOG.md b/CHANGELOG.md index a5ad0ea2d..52f0dd4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ and this project adheres to ## Unreleased +* [activity] Fix delete button for document (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/554) +* [activity] Add return path the document generation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/553) +* [person] add person ressource to person docgen normaliser (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/517) +* [person] AccompanyingCourseWorkEdit: fix deleting evaluation documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/546) +* [person] AccompanyingCourseWorkEdit: download existing documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/512) +* [person] AccompanyingCourseWorkEdit: replace document by a new one (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/511) +* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502) +* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495) * [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493) * [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477) * [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472) @@ -46,6 +54,11 @@ and this project adheres to * [thirdparty] For contacts show current civility/profession in edit form + fix saving of edited information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/491) * [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505) * [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486) +* [parcours] List of parcours for a specific user so they can be reassigned in case of absence (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/509) +* [thirdparty] Thirdparty view page, english text translated (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/534) +* [social_action] Translation changed in evaluation section (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/512) +* [household] Within parcours listing page of household add create button (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/560) +* [person_resource] bugfix when adding thirdparty or freetext resource (no issue) ## Test releases diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index 3858979ef..a89b1d67c 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -179,14 +179,15 @@ final class ActivityController extends AbstractController { $view = null; - [$person, $accompanyingPeriod] = $this->getEntity($request); - $entity = $this->activityRepository->find($id); if (null === $entity) { throw $this->createNotFoundException('Unable to find Activity entity.'); } + $accompanyingPeriod = $entity->getAccompanyingPeriod(); + $person = $entity->getPerson(); + if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) { $view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig'; $accompanyingPeriod = $entity->getAccompanyingPeriod(); @@ -220,6 +221,9 @@ final class ActivityController extends AbstractController $this->entityManager->persist($entity); $this->entityManager->flush(); + $params = $this->buildParamsToUrl($person, $accompanyingPeriod); + $params['id'] = $entity->getId(); + if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) { return $this->redirectToRoute( 'chill_docgenerator_generate_from_template', @@ -227,15 +231,13 @@ final class ActivityController extends AbstractController 'template' => $form->get('gendocTemplateId')->getData(), 'entityClassName' => Activity::class, 'entityId' => $entity->getId(), + 'returnPath' => $this->generateUrl('chill_activity_activity_edit', $params), ] ); } $this->addFlash('success', $this->get('translator')->trans('Success : activity updated!')); - $params = $this->buildParamsToUrl($person, $accompanyingPeriod); - $params['id'] = $entity->getId(); - return $this->redirectToRoute('chill_activity_activity_show', $params); } @@ -444,6 +446,9 @@ final class ActivityController extends AbstractController 'template' => $form->get('gendocTemplateId')->getData(), 'entityClassName' => Activity::class, 'entityId' => $entity->getId(), + 'returnPath' => $this->generateUrl('chill_activity_activity_edit', [ + 'id' => $entity->getId(), + ]), ] ); } diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 6f935ad99..891eb8577 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -305,6 +305,7 @@ class ActivityType extends AbstractType 'label' => $activityType->getLabel('documents'), 'required' => $activityType->isRequired('documents'), 'allow_add' => true, + 'allow_delete' => true, 'button_add_label' => 'activity.Insert a document', 'button_remove_label' => 'activity.Remove a document', 'empty_collection_explain' => 'No documents', diff --git a/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss b/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss new file mode 100644 index 000000000..16e66eadb --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss @@ -0,0 +1,16 @@ +div.chill-dropzone__below-zone { + a.btn-delete { + display: none; + } +} + +// do it in js does not work +// document.addEventListener('DOMContentLoaded', e => { +// const dropzoneBelow = document.querySelectorAll('div.chill-dropzone__below-zone'); +// dropzoneBelow.forEach( +// d => { +// const a = d.querySelector('a.btn-delete'); +// d.removeChild(a); +// } +// ) +// }); diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig index b278c0300..d0359e223 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/editAccompanyingCourse.html.twig @@ -30,4 +30,5 @@ {{ parent() }} {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('vue_activity') }} + {{ encore_entry_link_tags('page_edit_activity') }} {% endblock %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig index ac4266f8c..8b30bc6c7 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listPerson.html.twig @@ -46,7 +46,7 @@ {% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %} - {% if is_granted('CHILL_ACTIVITY_CREATE', person) %} + {% if is_granted('CHILL_ACTIVITY_CREATE_PERSON', person) %}
+ + + + + {% endfor %} + + {{ chill_pagination(paginator) }} + +{% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php index 1a5ae9afe..10acf2d6c 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Routing\MenuBuilder; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Knp\Menu\MenuItem; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -22,23 +23,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class SectionMenuBuilder implements LocalMenuBuilderInterface { - /** - * @var AuthorizationCheckerInterface - */ - protected $authorizationChecker; + protected AuthorizationCheckerInterface $authorizationChecker; - /** - * @var TranslatorInterface - */ - protected $translator; + protected ParameterBagInterface $parameterBag; + + protected TranslatorInterface $translator; /** * SectionMenuBuilder constructor. */ - public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator) + public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator, ParameterBagInterface $parameterBag) { $this->authorizationChecker = $authorizationChecker; $this->translator = $translator; + $this->parameterBag = $parameterBag; } /** @@ -54,14 +52,16 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface 'order' => 0, ]); - $menu->addChild($this->translator->trans('Global timeline'), [ - 'route' => 'chill_center_timeline', - ]) - ->setExtras( - [ - 'order' => 10, - ] - ); + if ($this->parameterBag->get('chill_main.access_global_history')) { + $menu->addChild($this->translator->trans('Global timeline'), [ + 'route' => 'chill_center_timeline', + ]) + ->setExtras( + [ + 'order' => 10, + ] + ); + } if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) { $menu->addChild($this->translator->trans('Export Menu'), [ diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CommentEmbeddableDocGenNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CommentEmbeddableDocGenNormalizer.php index bd812e036..2cddb73db 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CommentEmbeddableDocGenNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CommentEmbeddableDocGenNormalizer.php @@ -39,7 +39,7 @@ class CommentEmbeddableDocGenNormalizer implements ContextAwareNormalizerInterfa */ public function normalize($object, ?string $format = null, array $context = []): array { - if (null === $object) { + if (null === $object || ($object->isEmpty())) { return [ 'comment' => '', 'isNull' => true, @@ -52,7 +52,11 @@ class CommentEmbeddableDocGenNormalizer implements ContextAwareNormalizerInterfa ]; } - $user = $this->userRepository->find($object->getUserId()); + if (null === $object->getUserId()) { + $user = null; + } else { + $user = $this->userRepository->find($object->getUserId()); + } return [ 'comment' => $object->getComment(), diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php index 3562c1361..7eb323754 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php @@ -16,10 +16,10 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberUtil; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterface +class PhonenumberNormalizer implements ContextAwareNormalizerInterface, DenormalizerInterface { private string $defaultCarrierCode; @@ -53,6 +53,10 @@ class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterfac public function normalize($object, ?string $format = null, array $context = []): string { + if ('docgen' === $format && null === $object) { + return ''; + } + return $this->phoneNumberUtil->formatOutOfCountryCallingNumber($object, $this->defaultCarrierCode); } @@ -61,8 +65,18 @@ class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterfac return 'libphonenumber\PhoneNumber' === $type; } - public function supportsNormalization($data, ?string $format = null) + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { - return $data instanceof PhoneNumber; + if ($data instanceof PhoneNumber && 'json' === $format) { + return true; + } + + if ('docgen' === $format && ( + $data instanceof PhoneNumber || PhoneNumber::class === ($context['docgen:expects'] ?? null) + )) { + return true; + } + + return false; } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php index f5d76ef8f..42f541d56 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php @@ -100,10 +100,10 @@ final class UserControllerTest extends WebTestCase $crawler = $this->client->followRedirect(); // Check data in the show view - $this->assertGreaterThan( - 0, - $crawler->filter('td:contains("Test_user")')->count(), - 'Missing element td:contains("Test user")' + $this->assertStringContainsString( + 'Test_user', + $crawler->text(), + 'page contains the name of the user' ); //test the auth of the new client @@ -125,11 +125,7 @@ final class UserControllerTest extends WebTestCase $this->client->submit($form); $crawler = $this->client->followRedirect(); // Check the element contains an attribute with value equals "Foo" - $this->assertGreaterThan( - 0, - $crawler->filter('[data-username="' . $username . '"]')->count(), - 'Missing element [data-username="Foo bar"]' - ); + $this->assertResponseIsSuccessful(); } /** diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index aa3c5f512..b15f8adb9 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Controller; use Chill\ActivityBundle\Entity\Activity; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Form\AccompanyingCourseType; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; @@ -279,6 +280,40 @@ class AccompanyingCourseController extends Controller ]); } + /** + * @Route("/{_locale}/person/household/parcours/new", name="chill_household_accompanying_course_new") + */ + public function newHouseholdParcoursAction(Request $request): Response + { + $period = new AccompanyingPeriod(); + $em = $this->getDoctrine()->getManager(); + + if ($request->query->has('household_id')) { + $householdId = $request->query->get('household_id'); + + $household = $em->getRepository(Household::class)->find($householdId); + $members = $household->getCurrentMembers(); + + if (null !== $members) { + foreach ($members as $m) { + $period->addPerson($m->getPerson()); + } + } + } + + $userLocation = $this->getUser()->getCurrentLocation(); + $period->setAdministrativeLocation($userLocation); + + $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::CREATE, $period); + + $em->persist($period); + $em->flush(); + + return $this->redirectToRoute('chill_person_accompanying_course_edit', [ + 'accompanying_period_id' => $period->getId(), + ]); + } + /** * @Route("/{_locale}/parcours/{accompanying_period_id}/open", name="chill_person_accompanying_course_reopen") * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) diff --git a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php new file mode 100644 index 000000000..4abf5e93c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php @@ -0,0 +1,114 @@ +accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; + $this->engine = $engine; + $this->formFactory = $formFactory; + $this->paginatorFactory = $paginatorFactory; + $this->security = $security; + $this->userRepository = $userRepository; + $this->userRender = $userRender; + } + + /** + * @Route("/{_locale}/person/accompanying-periods/reassign", name="chill_course_list_reassign") + */ + public function listAction(Request $request): Response + { + if (!$this->security->isGranted('ROLE_USER') || !$this->security->getUser() instanceof User) { + throw new AccessDeniedException(); + } + + $form = $this->buildFilterForm(); + + $form->handleRequest($request); + + $total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod( + $form['user']->getData() + ); + $paginator = $this->paginatorFactory->create($total); + $periods = $this->accompanyingPeriodACLAwareRepository + ->findByUserOpenedAccompanyingPeriod( + $form['user']->getData(), + ['openingDate' => 'ASC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return new Response( + $this->engine->render('@ChillPerson/AccompanyingPeriod/reassign_list.html.twig', [ + 'paginator' => $paginator, + 'periods' => $periods, + 'form' => $form->createView(), + ]) + ); + } + + private function buildFilterForm(): FormInterface + { + $data = [ + 'user' => null, + ]; + $builder = $this->formFactory->createBuilder(FormType::class, $data, [ + '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, []); + }, + 'multiple' => false, + 'label' => 'User', + 'required' => false, + ]); + + return $builder->getForm(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 1f85e4ad0..6d2bf6d18 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -59,6 +59,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $config['allow_multiple_simultaneous_accompanying_periods'] ); + $container->setParameter( + 'chill_person.create_person_allowed', + $config['create_person_allowed'] + ); + + $container->setParameter( + 'chill_person.create_parcours_allowed', + $config['create_parcours_allowed'] + ); + // register all configuration in a unique parameter $container->setParameter('chill_person', $config); diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php index 8a0cab301..7b483a633 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/Configuration.php @@ -62,6 +62,12 @@ class Configuration implements ConfigurationInterface ->end() // children for 'validation', parent = validation ->end() //validation, parent = children of root ->end() // children of root, parent = root + ->booleanNode('create_person_allowed') + ->defaultTrue() + ->end() + ->booleanNode('create_parcours_allowed') + ->defaultTrue() + ->end() ->arrayNode('person_fields') ->canBeDisabled() ->children() diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index ed00c8611..95ec008d9 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -142,6 +142,15 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues */ private Collection $persons; + /** + * @ORM\ManyToMany(targetEntity=User::class) + * @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer") + * @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"}) + * @Serializer\Groups({"accompanying_period_work:edit"}) + * @Serializer\Groups({"accompanying_period_work:create"}) + */ + private Collection $referrers; + /** * @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks") * @ORM\JoinTable(name="chill_person_accompanying_period_work_result") @@ -196,6 +205,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues $this->thirdParties = new ArrayCollection(); $this->persons = new ArrayCollection(); $this->accompanyingPeriodWorkEvaluations = new ArrayCollection(); + $this->referrers = new ArrayCollection(); } public function addAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self @@ -227,6 +237,15 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues return $this; } + public function addReferrer(User $referrer): self + { + if (!$this->referrers->contains($referrer)) { + $this->referrers[] = $referrer; + } + + return $this; + } + public function addResult(Result $result): self { if (!$this->results->contains($result)) { @@ -308,6 +327,14 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues return $this->persons; } + /** + * @return Collection|User[] + */ + public function getReferrers(): Collection + { + return $this->referrers; + } + /** * @return Collection|Result[] */ @@ -382,6 +409,13 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues return $this; } + public function removeReferrer(User $referrer): self + { + $this->referrers->removeElement($referrer); + + return $this; + } + public function removeResult(Result $result): self { $this->results->removeElement($result); diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php index 07c9f526c..ed3200370 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php @@ -124,6 +124,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) * @Serializer\Groups({"read", "docgen:read"}) + * @Serializer\Groups({"write"}) * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) */ private ?DateTimeImmutable $maxDate = null; diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index fd07b0489..081ddc488 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -26,6 +26,7 @@ use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; use Chill\PersonBundle\Entity\Person\PersonCurrentAddress; +use Chill\PersonBundle\Entity\Person\PersonResource; use Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequential; use Chill\PersonBundle\Validator\Constraints\Person\Birthdate; use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter; @@ -451,6 +452,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI */ private $proxyAccompanyingPeriodOpenState = false; //TO-DELETE ? + /** + * @ORM\OneToMany(targetEntity=PersonResource::class, mappedBy="personOwner") + * + * @var Collection|PersonResource[]; + */ + private Collection $resources; + /** * The person's spoken languages. * @@ -493,6 +501,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI $this->maritalStatusComment = new CommentEmbeddable(); $this->periodLocatedOn = new ArrayCollection(); $this->accompanyingPeriodRequested = new ArrayCollection(); + $this->resources = new ArrayCollection(); } /** @@ -1301,6 +1310,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this->placeOfBirth; } + /** + * @return PersonResource[]|Collection + */ + public function getResources() + { + return $this->resources; + } + /** * Get spokenLanguages. * diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php index 3cd82680e..b1022cf4c 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResource.php @@ -19,7 +19,7 @@ use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\PersonBundle\Entity\Person; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -27,7 +27,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity * @ORM\Table(name="chill_person_resource") - * @DiscriminatorMap(typeProperty="type", mapping={ + * @Serializer\DiscriminatorMap(typeProperty="type", mapping={ * "personResource": personResource::class * }) */ @@ -39,13 +39,13 @@ class PersonResource implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_") - * @Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private CommentEmbeddable $comment; /** * @ORM\Column(type="text", nullable=true) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ private ?string $freeText = null; @@ -53,25 +53,30 @@ class PersonResource implements TrackCreationInterface, TrackUpdateInterface * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Groups({"read", "docgen:read"}) */ private ?int $id; /** * @ORM\ManyToOne(targetEntity=PersonResourceKind::class, inversedBy="personResources") * @ORM\JoinColumn(nullable=true) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $kind; + private ?PersonResourceKind $kind = null; /** - * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="personResources") + * The person which host the owner of this resource. + * + * @ORM\ManyToOne(targetEntity=Person::class) * @ORM\JoinColumn(nullable=true) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ private ?Person $person = null; /** - * @ORM\ManyToOne(targetEntity=Person::class) + * The person linked with this resource. + * + * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="resources") * @ORM\JoinColumn(nullable=false) * @Groups({"read"}) */ @@ -80,7 +85,7 @@ class PersonResource implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\ManyToOne(targetEntity=ThirdParty::class, inversedBy="personResources") * @ORM\JoinColumn(nullable=true) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ private ?ThirdParty $thirdParty = null; @@ -122,6 +127,26 @@ class PersonResource implements TrackCreationInterface, TrackUpdateInterface return $this->personOwner; } + /** + * @Groups({"read", "docgen:read"}) + */ + public function getResourceKind(): string + { + if ($this->getPerson() instanceof Person) { + return 'person'; + } + + if ($this->getThirdParty() instanceof ThirdParty) { + return 'thirdparty'; + } + + if (null !== $this->getFreeText()) { + return 'freetext'; + } + + return 'none'; + } + public function getThirdParty(): ?ThirdParty { return $this->thirdParty; @@ -205,5 +230,10 @@ class PersonResource implements TrackCreationInterface, TrackUpdateInterface $context->buildViolation('You must associate at least one entity') ->addViolation(); } + + if (null !== $this->person && $this->person === $this->personOwner) { + $context->buildViolation('You cannot associate a resource with the same person') + ->addViolation(); + } } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php index 4e65fda9f..f450997fa 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonResourceKind.php @@ -12,10 +12,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Entity\Person; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** - * **About denormalization**: this operation is operated by @see{AccompanyingPeriodResourdeNormalizer}. - * * @ORM\Entity * @ORM\Table(name="chill_person_resource_kind") */ @@ -25,8 +24,9 @@ class PersonResourceKind * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups({"docgen:read"}) */ - private int $id; + private ?int $id = null; /** * @ORM\Column(type="boolean") @@ -35,6 +35,8 @@ class PersonResourceKind /** * @ORM\Column(type="json", length=255) + * @Serializer\Groups({"docgen:read"}) + * @Serializer\Context({"is-translatable": true}, groups={"docgen:read"}) */ private array $title; diff --git a/src/Bundle/ChillPersonBundle/EventListener/AccompanyingPeriodWorkEventListener.php b/src/Bundle/ChillPersonBundle/EventListener/AccompanyingPeriodWorkEventListener.php new file mode 100644 index 000000000..83f9ca460 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/EventListener/AccompanyingPeriodWorkEventListener.php @@ -0,0 +1,33 @@ +security = $security; + } + + public function prePersistAccompanyingPeriodWork(AccompanyingPeriodWork $work): void + { + if ($this->security->getUser() instanceof User) { + $work->addReferrer($this->security->getUser()); + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php index 827dfb0a6..929fb2ae0 100644 --- a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -68,12 +68,14 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface } if ($this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $period)) { - $menu->addChild($this->translator->trans('Accompanying Course History'), [ - 'route' => 'chill_person_accompanying_course_history', - 'routeParameters' => [ - 'accompanying_period_id' => $period->getId(), - ], ]) - ->setExtras(['order' => 30]); + /* + $menu->addChild($this->translator->trans('Accompanying Course History'), [ + 'route' => 'chill_person_accompanying_course_history', + 'routeParameters' => [ + 'accompanying_period_id' => $period->getId(), + ], ]) + ->setExtras(['order' => 30]); + */ $menu->addChild($this->translator->trans('Accompanying Course Comment'), [ 'route' => 'chill_person_accompanying_period_comment_list', diff --git a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php index 04f989d84..357cf38be 100644 --- a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Knp\Menu\MenuItem; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -22,23 +23,18 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class SectionMenuBuilder implements LocalMenuBuilderInterface { - /** - * @var AuthorizationCheckerInterface - */ - protected $authorizationChecker; + protected AuthorizationCheckerInterface $authorizationChecker; - /** - * @var TranslatorInterface - */ - protected $translator; + protected TranslatorInterface $translator; /** * SectionMenuBuilder constructor. */ - public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator) + public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator, ParameterBagInterface $parameterBag) { $this->authorizationChecker = $authorizationChecker; $this->translator = $translator; + $this->parameterBag = $parameterBag; } /** @@ -46,7 +42,7 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface */ public function buildMenu($menuId, MenuItem $menu, array $parameters) { - if ($this->authorizationChecker->isGranted(PersonVoter::CREATE)) { + if ($this->authorizationChecker->isGranted(PersonVoter::CREATE) && $this->parameterBag->get('chill_person.create_person_allowed')) { $menu->addChild($this->translator->trans('Add a person'), [ 'route' => 'chill_person_new', ]) @@ -56,12 +52,22 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface ]); } - $menu->addChild($this->translator->trans('Create an accompanying course'), [ - 'route' => 'chill_person_accompanying_course_new', + if ($this->parameterBag->get('chill_person.create_parcours_allowed')) { + $menu->addChild($this->translator->trans('Create an accompanying course'), [ + 'route' => 'chill_person_accompanying_course_new', + ]) + ->setExtras([ + 'order' => 11, + 'icons' => ['plus'], + ]); + } + + $menu->addChild($this->translator->trans('Accompanying courses of users'), [ + 'route' => 'chill_course_list_reassign', ]) ->setExtras([ - 'order' => 11, - 'icons' => ['plus'], + 'order' => 12, + 'icons' => ['task'], ]); } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php index 3178aa6e2..3273e3a7d 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php @@ -87,9 +87,12 @@ class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository ->join('work.accompanyingPeriod', 'period') ->where( $qb->expr()->andX( - $qb->expr()->eq('period.user', ':user'), $qb->expr()->isNull('e.endDate'), - $qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')) + $qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')), + $qb->expr()->orX( + $qb->expr()->eq('period.user', ':user'), + $qb->expr()->isMemberOf(':user', 'work.referrers') + ) ) ) ->setParameters([ diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php index 9e67da458..dd27cf434 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php @@ -159,9 +159,12 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository ->join('w.accompanyingPeriod', 'period') ->where( $qb->expr()->andX( - $qb->expr()->eq('period.user', ':user'), $qb->expr()->gte('w.endDate', ':since'), - $qb->expr()->lte('w.startDate', ':until') + $qb->expr()->lte('w.startDate', ':until'), + $qb->expr()->orX( + $qb->expr()->eq('period.user', ':user'), + $qb->expr()->isMemberOf(':user', 'w.referrers') + ) ) ) ->setParameters([ diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index 6e6c3ca44..8efd60e58 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -11,12 +11,14 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; -use Symfony\Component\Security\Core\Security; +use DateTime; +use Symfony\Component\Security\Core\Security; use function count; final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodACLAwareRepositoryInterface @@ -41,6 +43,37 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC $this->centerResolverDispatcher = $centerResolverDispatcher; } + public function buildQueryOpenedAccompanyingCourseByUser(?User $user) + { + $qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap'); + + $qb->where($qb->expr()->eq('ap.user', ':user')) + ->andWhere( + $qb->expr()->neq('ap.step', ':draft'), + $qb->expr()->orX( + $qb->expr()->isNull('ap.closingDate'), + $qb->expr()->gt('ap.closingDate', ':now') + ) + ) + ->setParameter('user', $user) + ->setParameter('now', new DateTime('now')) + ->setParameter('draft', AccompanyingPeriod::STEP_DRAFT); + + return $qb; + } + + public function countByUserOpenedAccompanyingPeriod(?User $user): int + { + if (null === $user) { + return 0; + } + + return $this->buildQueryOpenedAccompanyingCourseByUser($user) + ->select('COUNT(ap)') + ->getQuery() + ->getSingleScalarResult(); + } + public function findByPerson( Person $person, string $role, @@ -92,4 +125,25 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC return $qb->getQuery()->getResult(); } + + /** + * @return array|AccompanyingPeriod[] + */ + public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array + { + if (null === $user) { + return []; + } + + $qb = $this->buildQueryOpenedAccompanyingCourseByUser($user); + + $qb->setFirstResult($offset) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('ap.' . $field, $direction); + } + + return $qb->getQuery()->getResult(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php index 778e8d8fe..a02ec27c6 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php @@ -11,10 +11,13 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; interface AccompanyingPeriodACLAwareRepositoryInterface { + public function countByUserOpenedAccompanyingPeriod(?User $user): int; + public function findByPerson( Person $person, string $role, @@ -22,4 +25,6 @@ interface AccompanyingPeriodACLAwareRepositoryInterface ?int $limit = null, ?int $offset = null ): array; + + public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array; } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index 122b658bd..ab7006fd8 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -78,6 +78,16 @@ final class AccompanyingPeriodRepository implements ObjectRepository ->getResult(); } + public function findConfirmedByUser(User $user) + { + $qb = $this->createQueryBuilder('ap'); + $qb->where($qb->expr()->eq('ap.user', ':user')) + ->andWhere('ap.step', 'CONFIRMED') + ->setParameter('user', $user); + + return $qb; + } + public function findOneBy(array $criteria): ?AccompanyingPeriod { return $this->findOneBy($criteria); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index a0d88d1bf..bcfa53e45 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -127,7 +127,7 @@{{ $t('no_referrers') }}
+Créé par {{ d.createdBy.text }}
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}
- {{ w.createdBy|chill_entity_render_box }} + {% for u in w.referrers %} + {{ u|chill_entity_render_box }} + {% if not loop.last %}, {% endif %} + {% endfor %}
{{ 'period_by_user_list.Pick a user'|trans }}
+ {% elseif periods|length == 0 and form.user.vars.value is not empty %} +{{ 'period_by_user_list.Any course or no authorization to see them'|trans }}
+ + {% else %} +{{ paginator.totalItems }} parcours à réassigner (calculé ce jour à {{ null|format_time('medium') }})
+ +