diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php index df996b7ad..0454bae69 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php @@ -34,6 +34,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Security; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -64,6 +65,8 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface private OnBehalfOfUserHttpClient $userHttpClient; + private Security $security; + public function __construct( CalendarRepository $calendarRepository, CalendarRangeRepository $calendarRangeRepository, @@ -74,7 +77,8 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface OnBehalfOfUserHttpClient $userHttpClient, RemoteEventConverter $remoteEventConverter, TranslatorInterface $translator, - UrlGeneratorInterface $urlGenerator + UrlGeneratorInterface $urlGenerator, + Security $security ) { $this->calendarRepository = $calendarRepository; $this->calendarRangeRepository = $calendarRangeRepository; @@ -86,6 +90,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface $this->translator = $translator; $this->urlGenerator = $urlGenerator; $this->userHttpClient = $userHttpClient; + $this->security = $security; } public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int @@ -133,6 +138,24 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface public function isReady(): bool { + $user = $this->security->getUser(); + + if (!$user instanceof User) { + // this is not a user from chill. This is not the role of this class to + // restrict access, so we will just say that we do not have to do anything more + // here... + return true; + } + + if (null === $this->mapCalendarToUser->getUserId($user)) { + // this user is not mapped with remote calendar. The user will have to wait for + // the next calendar subscription iteration + $this->logger->debug('mark user ready for msgraph calendar as he does not have any mapping', [ + 'userId' => $user->getId(), + ]); + return true; + } + return $this->tokenStorage->hasToken(); } diff --git a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php index 96787a6fc..c538bd107 100644 --- a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php +++ b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php @@ -95,6 +95,16 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler return null; } + public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array + { + $suggestedUsers = $entityWorkflow->getUsersInvolved(); + + $referrer = $this->getRelatedEntity($entityWorkflow)->getCourse()->getUser(); + $suggestedUsers[spl_object_hash($referrer)] = $referrer; + + return $suggestedUsers; + } + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string { return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig'; diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php index 17b5db75e..0f00a177f 100644 --- a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php @@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\TransitionBlocker; @@ -48,11 +49,13 @@ class WorkflowController extends AbstractController private Registry $registry; + private Security $security; + private TranslatorInterface $translator; private ValidatorInterface $validator; - public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator) + public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator, Security $security) { $this->entityWorkflowManager = $entityWorkflowManager; $this->entityWorkflowRepository = $entityWorkflowRepository; @@ -61,6 +64,7 @@ class WorkflowController extends AbstractController $this->registry = $registry; $this->entityManager = $entityManager; $this->translator = $translator; + $this->security = $security; } /** @@ -291,10 +295,18 @@ class WorkflowController extends AbstractController if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) { // possible transition + + $usersInvolved = $entityWorkflow->getUsersInvolved(); + $currentUserFound = array_search($this->security->getUser(), $usersInvolved, true); + + if (false !== $currentUserFound) { + unset($usersInvolved[$currentUserFound]); + } + $transitionForm = $this->createForm( WorkflowStepType::class, $entityWorkflow->getCurrentStep(), - ['transition' => true, 'entity_workflow' => $entityWorkflow] + ['transition' => true, 'entity_workflow' => $entityWorkflow, 'suggested_users' => $usersInvolved] ); $transitionForm->handleRequest($request); diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index c448ea19e..e4683f24b 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -348,6 +348,23 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface return $this->transitionningStep; } + /** + * @return User[] + */ + public function getUsersInvolved(): array + { + $usersInvolved = []; + $usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy(); + + foreach ($this->steps as $step) { + foreach ($step->getDestUser() as $u) { + $usersInvolved[spl_object_hash($u)] = $u; + } + } + + return $usersInvolved; + } + public function getWorkflowName(): string { return $this->workflowName; diff --git a/src/Bundle/ChillMainBundle/Form/Type/CommentType.php b/src/Bundle/ChillMainBundle/Form/Type/CommentType.php index 9b8fc4701..542a429c6 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/CommentType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/CommentType.php @@ -56,12 +56,7 @@ class CommentType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options) { - $view->vars = array_replace( - $view->vars, - [ - 'fullWidth' => true, - ] - ); + $view->vars['fullWidth'] = true; } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php index 2fbfdcf11..f6c2b7f4a 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerInterface; /** @@ -28,12 +29,15 @@ class PickUserDynamicType extends AbstractType { private DenormalizerInterface $denormalizer; + private NormalizerInterface $normalizer; + private SerializerInterface $serializer; - public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer) + public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer) { $this->denormalizer = $denormalizer; $this->serializer = $serializer; + $this->normalizer = $normalizer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -46,6 +50,11 @@ class PickUserDynamicType extends AbstractType $view->vars['multiple'] = $options['multiple']; $view->vars['types'] = ['user']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); + $view->vars['suggested'] = []; + + foreach ($options['suggested'] as $user) { + $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); + } } public function configureOptions(OptionsResolver $resolver) @@ -53,7 +62,8 @@ class PickUserDynamicType extends AbstractType $resolver ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) - ->setDefault('compound', false); + ->setDefault('compound', false) + ->setDefault('suggested', []); } public function getBlockPrefix() diff --git a/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php b/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php index 2ab318d9c..5c29f4d42 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PrivateCommentType.php @@ -39,7 +39,7 @@ class PrivateCommentType extends AbstractType $builder ->add('comments', ChillTextareaType::class, [ 'disable_editor' => $options['disable_editor'], - 'label' => false, + 'label' => $options['label'], ]) ->setDataMapper($this->dataMapper); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php index f65677dc7..79ed6df40 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php @@ -95,12 +95,7 @@ class ScopePickerType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options) { - $view->vars = array_replace( - $view->vars, - [ - 'fullWidth' => true, - ] - ); + $view->vars['fullWidth'] = true; } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php index a82c61fdc..1b18b3ef8 100644 --- a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php +++ b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php @@ -154,6 +154,7 @@ class WorkflowStepType extends AbstractType 'label' => 'workflow.dest for next steps', 'multiple' => true, 'mapped' => false, + 'suggested' => $options['suggested_users'], ]) ->add('future_dest_emails', ChillCollectionType::class, [ 'label' => 'workflow.dest by email', @@ -200,6 +201,7 @@ class WorkflowStepType extends AbstractType ->setAllowedTypes('transition', 'bool') ->setRequired('entity_workflow') ->setAllowedTypes('entity_workflow', EntityWorkflow::class) + ->setDefault('suggested_users', []) ->setDefault('constraints', [ new Callback( function ($step, ExecutionContextInterface $context, $payload) { 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 890929c35..6b143a11d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -23,7 +23,7 @@ function loadDynamicPicker(element) { (input.value === '[]' || input.value === '') ? null : [ JSON.parse(input.value) ] ) - ; + suggested = JSON.parse(el.dataset.suggested) if (!isMultiple) { if (input.value === '[]'){ @@ -37,6 +37,7 @@ function loadDynamicPicker(element) { ':types="types" ' + ':picked="picked" ' + ':uniqid="uniqid" ' + + ':suggested="notPickedSuggested" ' + '@addNewEntity="addNewEntity" ' + '@removeEntity="removeEntity">', components: { @@ -48,16 +49,31 @@ function loadDynamicPicker(element) { types: JSON.parse(el.dataset.types), picked: picked === null ? [] : picked, uniqid: el.dataset.uniqid, + suggested: suggested } }, + computed: { + notPickedSuggested() { + if (this.multiple) { + const pickedIds = new Set(); + for (const p of this.picked) { + pickedIds.add(`${p.type}${p.id}`); + } + return this.suggested.filter(e => !pickedIds.has(`${e.type}${e.id}`)) + } + + return this.suggested.filter(e => e.type !== this.picked.type && e.id !== e.picked.id); + } + }, methods: { - addNewEntity(entity) { + addNewEntity({entity}) { if (this.multiple) { if (!this.picked.some(el => { return el.type === entity.type && el.id === entity.id; })) { this.picked.push(entity); input.value = JSON.stringify(this.picked); + console.log(entity) } } else { if (!this.picked.some(el => { @@ -69,7 +85,10 @@ function loadDynamicPicker(element) { } } }, - removeEntity(entity) { + removeEntity({entity}) { + if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) { + this.suggested.push(entity); + } this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id)); input.value = JSON.stringify(this.picked); }, diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue index e121ca950..0a39718e2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue @@ -17,6 +17,9 @@ + diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/bootstrap5/bootstrap_5_horizontal_layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/bootstrap5/bootstrap_5_horizontal_layout.html.twig index f4a2c3294..1afdbb2c9 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/bootstrap5/bootstrap_5_horizontal_layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/bootstrap5/bootstrap_5_horizontal_layout.html.twig @@ -68,7 +68,8 @@ {{- form_errors(form) -}} {% else %} -
+
{{- form_label(form) -}}
+
{{- form_widget(form, widget_attr) -}} {{- form_help(form) -}} {{- form_errors(form) -}} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index 15d7625dd..28c55c9c1 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -18,43 +18,48 @@ {% block form_row %} {% apply spaceless %} - {% if form.vars.fullWidth is not defined or form.vars.fullWidth == false %}
-
+ {% if attr.class is not defined or ('cf-title' not in attr.class and 'cf-fields' not in attr.class ) %} + {{ form_label(form) }} {% endif %} - {% endapply %}"> - {% if attr.class is not defined or ('cf-title' not in attr.class and 'cf-fields' not in attr.class ) %} - {{ form_label(form) }} +
+
+ {{ form_widget(form) }} + {{ form_errors(form) }} +
+ {% else %} +
{{ form_label(form) }}
+
{{ form_widget(form) }}
{% endif %} -
-
- {{ form_widget(form) }} - {{ form_errors(form) }} -
- {% else %} - {{ form_widget(form) }} - {% endif %} {% endapply %} {% endblock form_row %} +{# + The block 'form_row' above may be removed ! + Read this note: https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/502#note_1311993084 +#} {% block choice_widget_expanded %} {% apply spaceless %} @@ -200,7 +205,6 @@ {% block private_comment_row %} - {{ form_label(form) }} {{ form_row(form) }} {% endblock %} @@ -211,7 +215,6 @@ {% endblock %} {% block comment_row %} - {{ form_label(form) }} {{ form_row(form) }} {% endblock %} @@ -249,7 +252,11 @@ {% block pick_entity_dynamic_widget %} -
+
{% endblock %} {% block pick_postal_code_widget %} @@ -269,4 +276,4 @@ {{ form_errors(form.fixedDate) }}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php index 5058fed7c..73e260c37 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php @@ -36,6 +36,11 @@ interface EntityWorkflowHandlerInterface */ public function getRoleShow(EntityWorkflow $entityWorkflow): ?string; + /** + * @return User[] + */ + public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array; + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string; public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array; diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index 4756ff4f7..263a57049 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -45,3 +45,12 @@ workflow: few {# workflows} other {# workflows} } + +duration: + minute: >- + {m, plural, + =0 {Aucune durée} + one {# minute} + few {# minutes} + other {# minutes} + } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php index 078ef0c3c..8780f7d17 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php @@ -160,6 +160,14 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU */ private ?DateInterval $warningInterval = null; + /** + * @ORM\Column(type="integer", nullable=true) + * @Serializer\Groups({"read", "docgen:read"}) + * @Serializer\Groups({"write"}) + * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) + */ + private ?int $timeSpent = null; + public function __construct() { $this->documents = new ArrayCollection(); @@ -265,6 +273,11 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU return $this->warningInterval; } + public function getTimeSpent(): ?int + { + return $this->timeSpent; + } + public function removeDocument(AccompanyingPeriodWorkEvaluationDocument $document): self { $this->documents->removeElement($document); @@ -322,6 +335,13 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU return $this; } + public function setTimeSpent(?int $timeSpent): self + { + $this->timeSpent = $timeSpent; + + return $this; + } + public function setEvaluation(?Evaluation $evaluation): AccompanyingPeriodWorkEvaluation { if ( diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php b/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php index e7d1e5dfd..010618733 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php @@ -47,6 +47,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface 'endDate', 'maxDate', 'warningInterval', + 'timeSpent', 'acpw_id', 'acpw_startDate', 'acpw_endDate', @@ -295,6 +296,9 @@ class ListEvaluation implements ListInterface, GroupedExportInterface $qb->addSelect(sprintf('workeval.%s AS %s', $field, $field)); } + // add the time spent field + $qb->addSelect('(workeval.timeSpent / 60) AS timeSpent'); + // those with identity foreach (['createdBy', 'updatedBy'] as $field) { $qb->addSelect(sprintf('IDENTITY(workeval.%s) AS %s', $field, $field)); diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php index efc005be0..a59220d74 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php @@ -19,7 +19,6 @@ use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -52,45 +51,39 @@ class SocialWorkTypeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - $where = $qb->getDQLPart('where'); - if (count($data['actionType']) > 0) { - $clause = $qb->expr()->in('acpw.socialAction', ':actionType'); - - if ($where instanceof Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } - - $qb->setParameter('actionType', $data['actionType']); + $qb + ->andWhere($qb->expr()->in('acpw.socialAction', ':actionType')) + ->setParameter('actionType', $data['actionType']); } if (count($data['goal']) > 0) { - if (!in_array('goal', $qb->getAllAliases(), true)) { - $qb->join('acpw.goals', 'goal'); + if (!in_array('acpw_goal', $qb->getAllAliases(), true)) { + $qb->join('acpw.goals', 'acpw_goal'); } - $where->add( - $qb->expr()->in('goal.id', ':goals') - ); + $orX = $qb->expr()->orX(); + foreach ($data['goal'] as $goal) { - $qb->setParameter('goals', $data['goal']); - } + /** @var Goal $goal */ + $andX = $qb->expr()->andX(); + $andX->add($qb->expr()->eq('acpw_goal.goal', $goalId = ':goal_'.uniqid())); + $qb->setParameter($goalId, $goal); - if (count($data['result']) > 0) { - if (!in_array('result', $qb->getAllAliases(), true)) { - $qb->join('acpw.results', 'result'); + if (count($data['result']) > 0) { + $orXResult = $qb->expr()->orX(); + foreach ($data['result'] as $result) { + + /** @var Result $result */ + $orXResult->add($qb->expr()->isMemberOf($resultId = ':result_'.uniqid(), 'acpw_goal.results')); + $qb->setParameter($resultId, $result); + } + $andX->add($orXResult); + } + $orX->add($andX); } - - $where->add( - $qb->expr()->in('result.id', ':results') - ); - - $qb->setParameter('results', $data['result']); + $qb->andWhere($orX); } - - $qb->add('where', $where); } public function applyOn(): string diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php index b0be9dd27..76891a74d 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php @@ -18,21 +18,25 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerInterface; /** - * Pick user dymically, using vuejs module "AddPerson". + * m* Pick user dymically, using vuejs module "AddPerson". */ class PickPersonDynamicType extends AbstractType { private DenormalizerInterface $denormalizer; + private DenormalizerInterface $normalizer; + private SerializerInterface $serializer; - public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer) + public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer) { $this->denormalizer = $denormalizer; $this->serializer = $serializer; + $this->normalizer = $normalizer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -45,6 +49,11 @@ class PickPersonDynamicType extends AbstractType $view->vars['multiple'] = $options['multiple']; $view->vars['types'] = ['person']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); + $view->vars['suggested'] = []; + + foreach ($options['suggested'] as $person) { + $view->vars['suggested'][] = $this->normalizer->normalize($person, 'json', ['groups' => 'read']); + } } public function configureOptions(OptionsResolver $resolver) @@ -52,7 +61,8 @@ class PickPersonDynamicType extends AbstractType $resolver ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) - ->setDefault('compound', false); + ->setDefault('compound', false) + ->setDefault('suggested', []); } public function getBlockPrefix() diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index d6e42f743..7dcb595a3 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -49,6 +49,20 @@ +
+ +
+ +
+
+
@@ -191,6 +205,8 @@ const i18n = { evaluation_choose_a_template: "Choisir un modèle", evaluation_add_a_document: "Ajouter un document", evaluation_add: "Ajouter une évaluation", + evaluation_time_spent: "Temps de rédaction", + select_time_spent: "Indiquez le temps de rédaction", Documents: "Documents", document_add: "Générer ou téléverser un document", document_upload: "Téléverser un document", @@ -224,6 +240,22 @@ export default { maxPostSize: 15000000, required: false, }, + timeSpentChoices: [ + { text: '1 minute', value: 60 }, { text: '2 minutes', value: 120 }, + { text: '3 minutes', value: 180 }, { text: '4 minutes', value: 240 }, + { text: '5 minutes', value: 300 }, { text: '10 minutes', value: 600 }, + { text: '15 minutes', value: 900 },{ text: '20 minutes', value: 1200 }, + { text: '25 minutes', value: 1500 }, { text: '30 minutes', value: 1800 }, + { text: '45 minutes', value: 2700 },{ text: '1 hour', value: 3600 }, + { text: '1 hour 15 minutes', value: 4500 }, { text: '1 hour 30 minutes', value: 5400 }, + { text: '1 hour 45 minutes', value: 6300 }, { text: '2 hours', value: 7200 }, + { text: '2 hours 30 minutes', value: 9000 }, { text: '3 hours', value: 10800 }, + { text: '3 hours 30 minutes', value: 12600 },{ text: '4 hours', value: 14400 }, + { text: '4 hours 30 minutes', value: 16200 },{ text: '5 hours', value: 18000 }, + { text: '5 hours 30 minutes', value: 19800 },{ text: '6 hours', value: 21600 }, + { text: '6 hours 30 minutes', value: 23400 },{ text: '7 hours', value: 25200 }, + { text: '7 hours 30 minutes', value: 27000 },{ text: '8 hours', value: 28800 }, + ] } }, computed: { @@ -265,6 +297,10 @@ export default { get() { return this.evaluation.warningInterval; }, set(v) { this.$store.commit('setEvaluationWarningInterval', { key: this.evaluation.key, days: v }); } }, + timeSpent: { + get() { return this.evaluation.timeSpent }, + set(v) { this.$store.commit('setEvaluationTimeSpent', { key: this.evaluation.key, time: v}) } + }, comment: { get() { return this.evaluation.comment; }, set(v) { this.$store.commit('setEvaluationComment', { key: this.evaluation.key, comment: v }); } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index d9f317b4c..e075e65bc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -116,6 +116,7 @@ const store = createStore({ endDate: e.endDate === null || e.endDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.endDate)) }, maxDate: e.maxDate === null || e.maxDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.maxDate)) }, warningInterval: intervalDaysToISO(e.warningInterval), + timeSpent: e.timeSpent, comment: e.comment, documents: e.documents }; @@ -138,6 +139,7 @@ const store = createStore({ endDate: e.endDate !== null ? dateToISO(new Date(e.endDate.datetime)) : null, maxDate: e.maxDate !== null ? dateToISO(new Date(e.maxDate.datetime)) : null, warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null, + timeSpent: e.timeSpent !== null ? e.timeSpent : null, documents: e.documents.map((d, docIndex) => { return Object.assign(d, { key: docIndex @@ -258,6 +260,7 @@ const store = createStore({ endDate: null, maxDate: null, warningInterval: null, + timeSpent: null, comment: "", editEvaluation: true, workflows_availables: state.work.workflows_availables_evaluation, @@ -286,6 +289,10 @@ const store = createStore({ state.evaluationsPicked.find(e => e.key === key) .warningInterval = days; }, + setEvaluationTimeSpent(state, {key, time}) { + state.evaluationsPicked.find(e => e.key === key) + .timeSpent = time; + }, setEvaluationComment(state, {key, comment}) { state.evaluationsPicked.find(e => e.key === key) .comment = comment; @@ -482,7 +489,7 @@ const store = createStore({ ; commit('setIsPosting', true); - console.log('payload', payload); + console.log('the social action', payload); return makeFetch('PUT', url, payload) .then(data => { diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig index 0dd0cc84a..f4198a5c7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig @@ -118,6 +118,18 @@ {% endif %} {% endif %} + + {% if e.timeSpent is not null and e.timeSpent > 0 %} +
  • + {% set minutes = (e.timeSpent / 60) %} + {{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }} {{ 'duration.minute'|trans({ '{m}' : minutes }) }} +
  • + {% elseif displayContent is defined and displayContent == 'long' %} +
  • + {{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }} + {{ 'Not given'|trans }} +
  • + {% endif %} @@ -143,7 +155,7 @@ {% else %} {{ 'No document found'|trans }} {% endif %} - + {% endif %} diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php index 735463fc2..500daab6f 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php @@ -97,6 +97,21 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW return AccompanyingPeriodWorkEvaluationDocumentVoter::SEE; } + public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array + { + $suggestedUsers = $entityWorkflow->getUsersInvolved(); + + $referrer = $this->getRelatedEntity($entityWorkflow) + ->getAccompanyingPeriodWorkEvaluation() + ->getAccompanyingPeriodWork() + ->getAccompanyingPeriod() + ->getUser(); + + $suggestedUsers[spl_object_hash($referrer)] = $referrer; + + return $suggestedUsers; + } + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string { return '@ChillPerson/Workflow/_evaluation_document.html.twig'; diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php index 25edce03b..db67ef045 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php @@ -87,6 +87,20 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH return AccompanyingPeriodWorkEvaluationVoter::SEE; } + public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array + { + $suggestedUsers = $entityWorkflow->getUsersInvolved(); + + $referrer = $this->getRelatedEntity($entityWorkflow) + ->getAccompanyingPeriodWork() + ->getAccompanyingPeriod() + ->getUser(); + + $suggestedUsers[spl_object_hash($referrer)] = $referrer; + + return $suggestedUsers; + } + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string { return '@ChillPerson/Workflow/_evaluation.html.twig'; diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php index b6829ab77..75ad04cfb 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php @@ -94,6 +94,19 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte return null; } + public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array + { + $suggestedUsers = $entityWorkflow->getUsersInvolved(); + + $referrer = $this->getRelatedEntity($entityWorkflow) + ->getAccompanyingPeriod() + ->getUser(); + + $suggestedUsers[spl_object_hash($referrer)] = $referrer; + + return $suggestedUsers; + } + public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string { return '@ChillPerson/Workflow/_accompanying_period_work.html.twig'; diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20230210104424.php b/src/Bundle/ChillPersonBundle/migrations/Version20230210104424.php new file mode 100644 index 000000000..ec802a993 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20230210104424.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation ADD timeSpent INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation DROP timeSpent'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 808ce5257..090f35c1c 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -908,6 +908,7 @@ accompanying_course_work: remove: Supprimer une action d'accompagnement social_evaluation: Évaluation private_comment: Commentaire privé + timeSpent: Temps de rédaction # @@ -1139,6 +1140,7 @@ export: updatedAt: Date de modification createdBy: Créé par updatedBy: Modifié par + timeSpent: Temps de rédaction (minutes) acpw: List of accompanying period works: Liste des actions List description: Génère une liste des actions d'accompagnement, filtrée sur différents paramètres. diff --git a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php index ada4064eb..eb9e7e5ea 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerInterface; /** @@ -27,12 +28,15 @@ class PickThirdpartyDynamicType extends AbstractType { private DenormalizerInterface $denormalizer; + private NormalizerInterface $normalizer; + private SerializerInterface $serializer; - public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer) + public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer) { $this->denormalizer = $denormalizer; $this->serializer = $serializer; + $this->normalizer = $normalizer; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -45,6 +49,11 @@ class PickThirdpartyDynamicType extends AbstractType $view->vars['multiple'] = $options['multiple']; $view->vars['types'] = ['thirdparty']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); + $view->vars['suggested'] = []; + + foreach ($options['suggested'] as $tp) { + $view->vars['suggested'][] = $this->normalizer->normalize($tp, 'json', ['groups' => 'read']); + } } public function configureOptions(OptionsResolver $resolver) @@ -52,7 +61,8 @@ class PickThirdpartyDynamicType extends AbstractType $resolver ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) - ->setDefault('compound', false); + ->setDefault('compound', false) + ->setDefault('suggested', []); } public function getBlockPrefix() diff --git a/src/Bundle/ChillWopiBundle/src/Controller/Editor.php b/src/Bundle/ChillWopiBundle/src/Controller/Editor.php index b0ff7fb4c..ab4276ec4 100644 --- a/src/Bundle/ChillWopiBundle/src/Controller/Editor.php +++ b/src/Bundle/ChillWopiBundle/src/Controller/Editor.php @@ -20,6 +20,7 @@ use Chill\WopiBundle\Service\Controller\ResponderInterface; use Exception; use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; use loophp\psr17\Psr17Interface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -81,7 +82,7 @@ final class Editor $this->router = $router; } - public function __invoke(string $fileId): Response + public function __invoke(string $fileId, Request $request): Response { if (null === $user = $this->security->getUser()) { throw new AccessDeniedHttpException('Please authenticate to access this feature'); @@ -151,6 +152,7 @@ final class Editor UrlGeneratorInterface::ABSOLUTE_URL ), 'closebutton' => 1, + 'lang' => $request->getLocale(), ] ) ); diff --git a/src/Bundle/ChillWopiBundle/src/Resources/views/Editor/page.html.twig b/src/Bundle/ChillWopiBundle/src/Resources/views/Editor/page.html.twig index 6534dfd30..c96833aa1 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/views/Editor/page.html.twig +++ b/src/Bundle/ChillWopiBundle/src/Resources/views/Editor/page.html.twig @@ -25,6 +25,7 @@
    +