From de4cd60b3d09b45d0cbc2f1cedc4affa0aff7f0f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 18 Jul 2022 12:52:02 +0200 Subject: [PATCH 01/16] query adjusted to fetch only active objectives for a social action --- .../Repository/SocialWork/GoalRepository.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php index ec74b1919..58608bb6c 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Goal; use Chill\PersonBundle\Entity\SocialWork\SocialAction; +use DateTime; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; @@ -73,6 +74,14 @@ final class GoalRepository implements ObjectRepository $qb = $this->buildQueryBySocialActionWithDescendants($action); $qb->select('g'); + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('g.desactivationDate'), + $qb->expr()->gt('g.desactivationDate', ':now') + ) + ) + ->setParameter('now', new DateTime('now')); + foreach ($orderBy as $sort => $order) { $qb->addOrderBy('g.' . $sort, $order); } From 62720005884a479e598a44923201692132e322b9 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 18 Jul 2022 12:53:20 +0200 Subject: [PATCH 02/16] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed9cdebf..fff655f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to * [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty * [search]: Order of birthdate fields changed in advanced search to avoid confusion. * [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675) +* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625) ## Test releases From 62dc9097080933ad966380d76eed16fe29ceb899 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 18 Jul 2022 14:51:40 +0200 Subject: [PATCH 03/16] php cs fixes, stan ok! --- .../ChillPersonBundle/Repository/SocialWork/GoalRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php index 58608bb6c..3dbf811f4 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php @@ -80,7 +80,7 @@ final class GoalRepository implements ObjectRepository $qb->expr()->gt('g.desactivationDate', ':now') ) ) - ->setParameter('now', new DateTime('now')); + ->setParameter('now', new DateTime('now')); foreach ($orderBy as $sort => $order) { $qb->addOrderBy('g.' . $sort, $order); From fd0e541e3f6ae67cf0681ae8b02e51766c5f390f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 29 Sep 2022 22:05:29 +0200 Subject: [PATCH 04/16] [workflow] Feature: allow user to retrieve the access link for the workflow + show dest email for a workflow --- CHANGELOG.md | 2 ++ .../views/Workflow/_decision.html.twig | 24 +++++++++++++++++++ .../views/Workflow/_history.html.twig | 9 +++++++ .../Authorization/EntityWorkflowVoter.php | 16 +++++++++++++ .../translations/messages.fr.yml | 6 ++++- 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 030b4ba33..9c8cc8379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to ## Unreleased +* [workflow] Feature: allow user to copy and send manually the access link for the workflow +* [workflow] Feature: show the email addresses that received an access link for the workflow ## Test releases diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig index bb0411371..bd9274739 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig @@ -95,6 +95,15 @@ {% endif %} + {% if entity_workflow.currentStep.destEmail|length > 0 %} +

{{ 'workflow.An access key was also sent to those addresses'|trans }} :

+
    + {% for e in entity_workflow.currentStep.destEmail -%} +
  • {{ e }}
  • + {%- endfor %} +
+ {% endif %} + {% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}

{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :

    @@ -103,6 +112,21 @@ {% endfor %}
{% endif %} + + {% if is_granted('CHILL_MAIN_WORKFLOW_LINK_SHOW', entity_workflow) %} +

{{ 'workflow.This link grant any user to apply a transition'|trans }} :

+ + {% set link = absolute_url(path('chill_main_workflow_grant_access_by_key', {'id': entity_workflow.currentStep.id, 'accessKey': entity_workflow.currentStep.accessKey})) %} +
+ + + + +
+ {% endif %} + + {% endif %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig index dda309da0..72aab397c 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig @@ -81,6 +81,15 @@ {% endif %} + {% if entity_workflow.currentStep.destEmail|length > 0 %} +

{{ 'workflow.An access key was also sent to those addresses'|trans }} :

+
    + {% for e in entity_workflow.currentStep.destEmail -%} +
  • {{ e }}
  • + {%- endfor %} +
+ {% endif %} + {% if step.destUserByAccessKey|length > 0 %}

{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :

    diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php index 82dfc7681..c34022359 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php @@ -27,6 +27,8 @@ class EntityWorkflowVoter extends Voter public const SEE = 'CHILL_MAIN_WORKFLOW_SEE'; + public const SHOW_ENTITY_LINK = 'CHILL_MAIN_WORKFLOW_LINK_SHOW'; + private EntityWorkflowManager $manager; private Security $security; @@ -80,6 +82,19 @@ class EntityWorkflowVoter extends Voter case self::DELETE: return $subject->getStep() === 'initial'; + case self::SHOW_ENTITY_LINK: + if ($subject->getStep() === 'initial') { + return false; + } + + $currentStep = $subject->getCurrentStepChained(); + + if ($currentStep->isFinal()) { + return false; + } + + return $currentStep->getPrevious()->getTransitionBy() === $this->security->getUser(); + default: throw new UnexpectedValueException("attribute {$attribute} not supported"); } @@ -91,6 +106,7 @@ class EntityWorkflowVoter extends Voter self::SEE, self::CREATE, self::DELETE, + self::SHOW_ENTITY_LINK, ]; } } diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 1e95e32d0..36f793817 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -454,7 +454,6 @@ workflow: Delete workflow: Supprimer le workflow Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ? You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow. - Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès dest by email: Liens d'autorisation par email dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape. Add an email: Ajouter une adresse email @@ -466,6 +465,11 @@ workflow: Previous workflow transitionned help: Workflows où vous avez exécuté une action. For: Pour You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire. + An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces addresses + Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtennu l'accès grâce au lien reçu par email + Access link copied: Lien d'accès copié + This link grant any user to apply a transition: Le lien d'accès suivant permet d'appliquer une transition + The workflow may be accssed through this link: Une transition peut être appliquée sur ce workflow grâce au lien d'accès suivant Subscribe final: Recevoir une notification à l'étape finale From 713b8357cddf5b0cc2f03707cff397f18956d5dc Mon Sep 17 00:00:00 2001 From: nobohan Date: Thu, 6 Oct 2022 15:26:44 +0200 Subject: [PATCH 05/16] [person]: add new test for social action normalizer --- .../Normalizer/SocialActionNormalizerTest.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/SocialActionNormalizerTest.php diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/SocialActionNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/SocialActionNormalizerTest.php new file mode 100644 index 000000000..9d43eaaf8 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/SocialActionNormalizerTest.php @@ -0,0 +1,48 @@ +normalizer = self::$container->get(NormalizerInterface::class); + } + + public function testNormalization() + { + $sa = new SocialAction(); + + $normalized = $this->normalizer->normalize( + $sa, + 'json', + ['groups' => ['read']] + ); + + $this->assertIsArray($normalized); + $this->assertArrayHasKey('type', $normalized); + $this->assertEquals('social_work_social_action', $normalized['type']); + } +} From f82bc02f8bda3d38c8cf30c9c1858b8b5dbca3a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 6 Oct 2022 20:46:57 +0200 Subject: [PATCH 06/16] Feature: Allow to filter periods to reassign by postal code --- .../Controller/PostalCodeAPIController.php | 6 +- .../PostalCodeToIdTransformer.php | 55 +++++++++ .../Form/Type/PickPostalCodeType.php | 49 ++++++++ .../Repository/PostalCodeRepository.php | 16 +-- .../PostalCodeRepositoryInterface.php | 42 +++++++ .../public/module/pick-postal-code/index.js | 66 +++++++++++ .../vuejs/PickPostalCode/PickPostalCode.md | 4 + .../vuejs/PickPostalCode/PickPostalCode.vue | 108 ++++++++++++++++++ .../vuejs/PickPostalCode/_PickPostalCode.scss | 3 + .../public/vuejs/PickPostalCode/api.js | 43 +++++++ .../Resources/views/Form/fields.html.twig | 6 + .../Form/Type/PickPostalCodeTypeTest.php | 67 +++++++++++ .../ChillMainBundle/chill.webpack.config.js | 1 + .../ReassignAccompanyingPeriodController.php | 15 ++- .../AccompanyingPeriodACLAwareRepository.php | 74 +++++++++++- ...nyingPeriodACLAwareRepositoryInterface.php | 11 ++ .../reassign_list.html.twig | 4 + .../translations/messages.fr.yml | 2 + 18 files changed, 553 insertions(+), 19 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php create mode 100644 src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php create mode 100644 src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php create mode 100644 src/Bundle/ChillMainBundle/Resources/public/module/pick-postal-code/index.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js create mode 100644 src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php diff --git a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php index 437f0930c..8e679b228 100644 --- a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php +++ b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php @@ -14,7 +14,7 @@ namespace Chill\MainBundle\Controller; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\CountryRepository; -use Chill\MainBundle\Repository\PostalCodeRepository; +use Chill\MainBundle\Repository\PostalCodeRepositoryInterface; use Chill\MainBundle\Serializer\Model\Collection; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -30,11 +30,11 @@ final class PostalCodeAPIController extends ApiController private PaginatorFactory $paginatorFactory; - private PostalCodeRepository $postalCodeRepository; + private PostalCodeRepositoryInterface $postalCodeRepository; public function __construct( CountryRepository $countryRepository, - PostalCodeRepository $postalCodeRepository, + PostalCodeRepositoryInterface $postalCodeRepository, PaginatorFactory $paginatorFactory ) { $this->countryRepository = $countryRepository; diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php new file mode 100644 index 000000000..ca488fe1a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php @@ -0,0 +1,55 @@ +postalCodeRepository = $postalCodeRepository; + } + + public function reverseTransform($value) + { + if (null === $value || trim('') === $value) { + return null; + } + + if (!is_int((int) $value)) { + throw new TransformationFailedException('Cannot transform ' . gettype($value)); + } + + return $this->postalCodeRepository->find((int) $value); + } + + public function transform($value) + { + if (null === $value) { + return null; + } + + if ($value instanceof PostalCode) { + return $value->getId(); + } + + throw new TransformationFailedException('Could not reverseTransform ' . gettype($value)); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php b/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php new file mode 100644 index 000000000..d1feacd6a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php @@ -0,0 +1,49 @@ +postalCodeToIdTransformer = $postalCodeToIdTransformer; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addViewTransformer($this->postalCodeToIdTransformer); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['uniqid'] = $view->vars['attr']['data-input-postal-code'] = uniqid('input_pick_postal_code_'); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', PostalCode::class) + ->setDefault('multiple', false) + ->setAllowedTypes('multiple', ['bool']) + ->setDefault('compound', false); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php index c53df01df..32c1322be 100644 --- a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php @@ -18,10 +18,9 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\ResultSetMappingBuilder; -use Doctrine\Persistence\ObjectRepository; use RuntimeException; -final class PostalCodeRepository implements ObjectRepository +final class PostalCodeRepository implements PostalCodeRepositoryInterface { private EntityManagerInterface $entityManager; @@ -29,7 +28,7 @@ final class PostalCodeRepository implements ObjectRepository public function __construct(EntityManagerInterface $entityManager) { - $this->repository = $entityManager->getRepository(PostalCode::class); + $this->repository = $entityManager->getRepository($this->getClassName()); $this->entityManager = $entityManager; } @@ -51,20 +50,11 @@ final class PostalCodeRepository implements ObjectRepository return $this->repository->find($id, $lockMode, $lockVersion); } - /** - * @return PostalCode[] - */ public function findAll(): array { return $this->repository->findAll(); } - /** - * @param mixed|null $limit - * @param mixed|null $offset - * - * @return PostalCode[] - */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); @@ -95,7 +85,7 @@ final class PostalCodeRepository implements ObjectRepository return $this->repository->findOneBy($criteria, $orderBy); } - public function getClassName() + public function getClassName(): string { return PostalCode::class; } diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php new file mode 100644 index 000000000..fe3dee195 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php @@ -0,0 +1,42 @@ +', + components: { + PickPostalCode, + }, + data() { + return { + city: city, + } + }, + methods: { + onCitySelected(city) { + this.city = city; + input.value = city.id; + }, + onCityRemoved(city) { + this.city = null; + input.value = ''; + } + } + }) + .use(i18n) + .mount(el); +} + +function loadDynamicPickers(element) { + + let apps = element.querySelectorAll('[data-module="pick-postal-code"]'); + + apps.forEach(function(el) { + + console.log('el', el); + const + uniqId = el.dataset.uniqid, + input = document.querySelector(`input[data-input-uniqid="${uniqId}"]`), + cityIdValue = input.value === '' ? null : input.value + ; + + console.log('uniqid', uniqId); + console.log('input', input); + console.log('input value', input.value); + console.log('cityIdValue', cityIdValue); + + if (cityIdValue !== null) { + makeFetch('GET', `/api/1.0/main/postal-code/${cityIdValue}.json`).then(city => { + loadOnePicker(el, input, uniqId, city); + }) + } else { + loadOnePicker(el, input, uniqId, null); + } + }); +} + +document.addEventListener('DOMContentLoaded', function(e) { + loadDynamicPickers(document) +}) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md new file mode 100644 index 000000000..93dcf1816 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md @@ -0,0 +1,4 @@ +# Pickpostalcode + +## Usage + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue new file mode 100644 index 000000000..3c191b8c4 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss new file mode 100644 index 000000000..09f5fd539 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss @@ -0,0 +1,3 @@ +.PickPostalCode { + +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js new file mode 100644 index 000000000..d31dcc3f4 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js @@ -0,0 +1,43 @@ +import {makeFetch, fetchResults} from 'ChillMainAssets/lib/api/apiMethods'; + +/** + * Endpoint chill_api_single_postal_code__index + * method GET, get Cities Object + * @params {object} a country object + * @returns {Promise} a promise containing all Postal Code objects filtered with country + */ +const fetchCities = (country) => { + // warning: do not use fetchResults (in apiMethods): we need only a **part** of the results in the db + const params = new URLSearchParams({item_per_page: 100}); + + if (country !== null) { + params.append('country', country.id); + } + + return makeFetch('GET', `/api/1.0/main/postal-code.json?${params.toString()}`).then(r => Promise.resolve(r.results)); +}; + +/** + * Endpoint chill_main_postalcodeapi_search + * method GET, get Cities Object + * @params {string} search a search string + * @params {object} country a country object + * @params {AbortController} an abort controller + * @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string + */ +const searchCities = (search, country, controller) => { + const url = '/api/1.0/main/postal-code/search.json?'; + const params = new URLSearchParams({q: search}); + + if (country !== null) { + Object.assign('country', country.id); + } + + return makeFetch('GET', url + params, null, {signal: controller.signal}) + .then(result => Promise.resolve(result.results)); +}; + +export { + fetchCities, + searchCities, +}; diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index 586b900aa..7ab8a4c85 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -238,3 +238,9 @@
    {% endblock %} + +{% block pick_postal_code_widget %} + {{ form_help(form)}} + +
    +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php new file mode 100644 index 000000000..9c0174754 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php @@ -0,0 +1,67 @@ +factory->create(PickPostalCodeType::class, null); + + $form->submit(['1']); + + $this->assertTrue($form->isSynchronized()); + + $this->assertEquals(1, $form->getData()->getId()); + } + + protected function getExtensions() + { + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $postalCodeRepository->find(Argument::type('string')) + ->will(static function ($args) { + $postalCode = new PostalCode(); + $reflectionClass = new ReflectionClass($postalCode); + $id = $reflectionClass->getProperty('id'); + $id->setAccessible(true); + $id->setValue($postalCode, (int) $args[0]); + + return $postalCode; + }); + + $type = new PickPostalCodeType( + new PostalCodeToIdTransformer( + $postalCodeRepository->reveal() + ) + ); + + return [ + new PreloadedExtension([$type], []), + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 628f04ba5..73f539739 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -70,6 +70,7 @@ module.exports = function(encore, entries) encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js'); encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js'); encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js'); + encore.addEntry('mod_pick_postal_code', __dirname + '/Resources/public/module/pick-postal-code/index.js'); // Vue entrypoints encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js'); diff --git a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php index 181677709..c27362e17 100644 --- a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php +++ b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php @@ -11,7 +11,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; +use Chill\MainBundle\Entity\PostalCode; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Form\Type\PickPostalCodeType; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\UserRepository; @@ -92,12 +94,14 @@ class ReassignAccompanyingPeriodController extends AbstractController $form->handleRequest($request); $userFrom = $form['user']->getData(); + $postalCodes = $form['postal_code']->getData() instanceof PostalCode ? [$form['postal_code']->getData()] : []; $total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod($userFrom); $paginator = $this->paginatorFactory->create($total); $periods = $this->accompanyingPeriodACLAwareRepository - ->findByUserOpenedAccompanyingPeriod( + ->findByUserAndPostalCodesOpenedAccompanyingPeriod( $userFrom, + $postalCodes, ['openingDate' => 'ASC'], $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber() @@ -148,7 +152,9 @@ class ReassignAccompanyingPeriodController extends AbstractController { $data = [ 'user' => null, + 'postal_code' => null, ]; + $builder = $this->formFactory->createBuilder(FormType::class, $data, [ 'method' => 'get', 'csrf_protection' => false, ]); @@ -158,12 +164,17 @@ class ReassignAccompanyingPeriodController extends AbstractController 'label' => 'reassign.Current user', 'required' => false, 'help' => 'reassign.Choose a user and click on "Filter" to apply', + ]) + ->add('postal_code', PickPostalCodeType::class, [ + 'label' => 'reassign.Filter by postal code', + 'required' => false, + 'help' => 'reassign.Filter course which are located inside a postal code', ]); return $builder->getForm(); } - private function buildReassignForm(array $periodIds, ?User $userFrom): FormInterface + private function buildReassignForm(array $periodIds, ?User $userFrom = null): FormInterface { $defaultData = [ 'userFrom' => $userFrom, diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index f5217bfa7..071f7b3eb 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -11,7 +11,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Location; +use Chill\MainBundle\Entity\PostalCode; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\UserJob; @@ -19,10 +21,14 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; +use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use DateTime; +use DateTimeImmutable; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Security\Core\Security; use function count; @@ -49,7 +55,12 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC $this->centerResolverDispatcher = $centerResolverDispatcher; } - public function buildQueryOpenedAccompanyingCourseByUser(?User $user) + /** + * @param array|PostalCode[] + * + * @return QueryBuilder + */ + public function buildQueryOpenedAccompanyingCourseByUser(?User $user, array $postalCodes = []) { $qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap'); @@ -65,6 +76,37 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC ->setParameter('now', new DateTime('now')) ->setParameter('draft', AccompanyingPeriod::STEP_DRAFT); + if ([] !== $postalCodes) { + $qb->join('ap.locationHistories', 'location_history') + ->leftJoin(PersonHouseholdAddress::class, 'person_address', Join::WITH, 'IDENTITY(location_history.personLocation) = IDENTITY(person_address.person)') + ->join( + Address::class, + 'address', + Join::WITH, + 'COALESCE(IDENTITY(location_history.addressLocation), IDENTITY(person_address.address)) = address.id' + ) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('person_address'), + $qb->expr()->andX( + $qb->expr()->lte('person_address.validFrom', ':now'), + $qb->expr()->orX( + $qb->expr()->isNull('person_address.validTo'), + $qb->expr()->lt('person_address.validTo', ':now') + ) + ) + ) + ) + ->andWhere( + $qb->expr()->isNull('location_history.endDate') + ) + ->andWhere( + $qb->expr()->in('address.postcode', ':postal_codes') + ) + ->setParameter('now', new DateTimeImmutable('now'), Types::DATE_IMMUTABLE) + ->setParameter('postal_codes', $postalCodes); + } + return $qb; } @@ -77,6 +119,18 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC return $qb->getQuery()->getSingleScalarResult(); } + public function countByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes): int + { + if (null === $user) { + return 0; + } + + return $this->buildQueryOpenedAccompanyingCourseByUser($user, $postalCodes) + ->select('COUNT(ap)') + ->getQuery() + ->getSingleScalarResult(); + } + public function countByUserOpenedAccompanyingPeriod(?User $user): int { if (null === $user) { @@ -158,6 +212,24 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC return $qb->getQuery()->getResult(); } + public function findByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes, 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(); + } + /** * @return array|AccompanyingPeriod[] */ diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php index 6dd44b290..0cca1a5f4 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\PostalCode; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\UserJob; @@ -25,6 +26,11 @@ interface AccompanyingPeriodACLAwareRepositoryInterface */ public function countByUnDispatched(array $jobs, array $services, array $administrativeLocations): int; + /** + * @param array|PostalCode[] $postalCodes + */ + public function countByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes): int; + public function countByUserOpenedAccompanyingPeriod(?User $user): int; public function findByPerson( @@ -43,5 +49,10 @@ interface AccompanyingPeriodACLAwareRepositoryInterface */ public function findByUnDispatched(array $jobs, array $services, array $administrativeLocations, ?int $limit = null, ?int $offset = null): array; + /** + * @param array|PostalCode[] $postalCodes + */ + public function findByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes, array $orderBy = [], int $limit = 0, int $offset = 50): array; + public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): 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 63e25efeb..fb22f5153 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig @@ -5,11 +5,13 @@ {% block js %} {{ encore_entry_script_tags('mod_set_referrer') }} {{ encore_entry_script_tags('mod_pickentity_type') }} + {{ encore_entry_script_tags('mod_pick_postal_code') }} {% endblock %} {% block css %} {{ encore_entry_link_tags('mod_set_referrer') }} {{ encore_entry_link_tags('mod_pickentity_type') }} + {{ encore_entry_link_tags('mod_pick_postal_code') }} {% endblock %} {% macro period_meta(period) %} @@ -48,6 +50,8 @@ {{ form_start(form) }} {{ form_label(form.user ) }} {{ form_widget(form.user, {'attr': {'class': 'select2'}}) }} + {{ form_label(form.postal_code) }} + {{ form_widget(form.postal_code) }}