diff --git a/.changes/unreleased/Fixed-20230621-132851.yaml b/.changes/unreleased/Fixed-20230621-132851.yaml new file mode 100644 index 000000000..89fb7cdd9 --- /dev/null +++ b/.changes/unreleased/Fixed-20230621-132851.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: '[Accompanying period comments]: order comments from the most recent to the + oldest, in the list' +time: 2023-06-21T13:28:51.282714011+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/Fixed-20230621-135912.yaml b/.changes/unreleased/Fixed-20230621-135912.yaml new file mode 100644 index 000000000..676d1c21b --- /dev/null +++ b/.changes/unreleased/Fixed-20230621-135912.yaml @@ -0,0 +1,5 @@ +kind: Fixed +body: 'Api: filter social action to keep only the currently activated' +time: 2023-06-21T13:59:12.57760217+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/Fixed-20230621-141828.yaml b/.changes/unreleased/Fixed-20230621-141828.yaml new file mode 100644 index 000000000..2c7f94488 --- /dev/null +++ b/.changes/unreleased/Fixed-20230621-141828.yaml @@ -0,0 +1,5 @@ +kind: Fixed +body: Fix deletion and re-creation of filiation relationship +time: 2023-06-21T14:18:28.437876316+02:00 +custom: + Issue: "82" diff --git a/.changes/v2.1.0.md b/.changes/v2.1.0.md new file mode 100644 index 000000000..ade83aee0 --- /dev/null +++ b/.changes/v2.1.0.md @@ -0,0 +1,17 @@ +## v2.1.0 - 2023-06-12 + +### Feature + +* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod + +### Fixed + +* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods + +### Security + +* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context + +### DX + +* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping diff --git a/.changes/v2.2.0.md b/.changes/v2.2.0.md new file mode 100644 index 000000000..5371cad35 --- /dev/null +++ b/.changes/v2.2.0.md @@ -0,0 +1,12 @@ +## v2.2.0 - 2023-06-18 +### Feature +* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document +* Add notification to accompanying period work and work's evaluation's documents +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates +### Fixed +* use the correct annotation for the association between PersonCurrentCenter and Person +* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox +* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter +### DX +* DQL function OVERLAPSI: simplify expression in postgresql diff --git a/.changes/v2.2.1.md b/.changes/v2.2.1.md new file mode 100644 index 000000000..c96b18585 --- /dev/null +++ b/.changes/v2.2.1.md @@ -0,0 +1,3 @@ +## v2.2.1 - 2023-06-19 +### Fixed +* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation diff --git a/.changie.yaml b/.changie.yaml index 83fd9770a..ba5454ce7 100644 --- a/.changie.yaml +++ b/.changie.yaml @@ -6,7 +6,7 @@ versionExt: md versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}' kindFormat: '### {{.Kind}}' changeFormat: >- - * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{- end }}{{.Body}} + * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} custom: - key: Issue label: Issue number (on chill-bundles repository) (optional) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5475b20b8..e223fa116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,41 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v2.2.1 - 2023-06-19 +### Fixed +* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation + +## v2.2.0 - 2023-06-18 +### Feature +* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document +* Add notification to accompanying period work and work's evaluation's documents +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps +* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates +### Fixed +* use the correct annotation for the association between PersonCurrentCenter and Person +* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox +* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter +### DX +* DQL function OVERLAPSI: simplify expression in postgresql + +## v2.1.0 - 2023-06-12 + +### Feature + +* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod + +### Fixed + +* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods + +### Security + +* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context + +### DX + +* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping + ## 2.0.0 * this is a release to relaunch our proceess of release with semantic versioning diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue index aa99a223f..be482cf7e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue @@ -44,3 +44,9 @@ async function download_and_open(event: Event): Promise { } + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue index 10985fdff..0248ef30b 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue @@ -88,3 +88,9 @@ async function download_and_open(event: Event): Promise { console.log('open button should have been clicked'); } + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue index d68f60f86..ea244192e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue @@ -40,5 +40,8 @@ async function beforeLeave(event: Event): Promise { diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig index f83cafd51..2babe1ad9 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig @@ -1,5 +1,5 @@ {%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} -
dispatch($sqlWalker), + "COALESCE(%s, '%s'::date)", $part->dispatch($sqlWalker), $p ); } return sprintf( - "CASE WHEN %s::date IS NOT NULL THEN %s::date ELSE '%s'::date END", - $part->dispatch($sqlWalker), + "COALESCE(%s::date, '%s'::date)", $part->dispatch($sqlWalker), $p ); diff --git a/src/Bundle/ChillMainBundle/Entity/Center.php b/src/Bundle/ChillMainBundle/Entity/Center.php index 5ca051ec0..0d5402409 100644 --- a/src/Bundle/ChillMainBundle/Entity/Center.php +++ b/src/Bundle/ChillMainBundle/Entity/Center.php @@ -48,12 +48,19 @@ class Center implements HasCenterInterface */ private string $name = ''; + /** + * @var Collection + * @ORM\ManyToMany(targetEntity=Regroupment::class, mappedBy="centers") + */ + private Collection $regroupments; + /** * Center constructor. */ public function __construct() { - $this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection(); + $this->groupCenters = new ArrayCollection(); + $this->regroupments = new ArrayCollection(); } /** @@ -106,6 +113,14 @@ class Center implements HasCenterInterface return $this->name; } + /** + * @return Collection + */ + public function getRegroupments(): Collection + { + return $this->regroupments; + } + /** * @param $name * diff --git a/src/Bundle/ChillMainBundle/Entity/Regroupment.php b/src/Bundle/ChillMainBundle/Entity/Regroupment.php index 96953abf5..c5d9525cf 100644 --- a/src/Bundle/ChillMainBundle/Entity/Regroupment.php +++ b/src/Bundle/ChillMainBundle/Entity/Regroupment.php @@ -22,11 +22,12 @@ use Doctrine\ORM\Mapping as ORM; class Regroupment { /** - * @var Center * @ORM\ManyToMany( - * targetEntity=Center::class + * targetEntity=Center::class, + * inversedBy="regroupments" * ) * @ORM\Id + * @var Collection
*/ private Collection $centers; @@ -52,6 +53,26 @@ class Regroupment $this->centers = new ArrayCollection(); } + public function addCenter(Center $center): self + { + if (!$this->centers->contains($center)) { + $this->centers->add($center); + $center->getRegroupments()->add($this); + } + + return $this; + } + + public function removeCenter(Center $center): self + { + if ($this->centers->contains($center)) { + $this->centers->removeElement($center); + $center->getRegroupments()->removeElement($this); + } + + return $this; + } + public function getCenters(): Collection { return $this->centers; diff --git a/src/Bundle/ChillMainBundle/Form/RegroupmentType.php b/src/Bundle/ChillMainBundle/Form/RegroupmentType.php index bc8e6684f..22814598a 100644 --- a/src/Bundle/ChillMainBundle/Form/RegroupmentType.php +++ b/src/Bundle/ChillMainBundle/Form/RegroupmentType.php @@ -31,7 +31,7 @@ class RegroupmentType extends AbstractType ->add('centers', EntityType::class, [ 'class' => Center::class, 'multiple' => true, - 'attr' => ['class' => 'select2'], + 'expanded' => true, ]) ->add('isActive', CheckboxType::class, [ 'label' => 'Actif ?', diff --git a/src/Bundle/ChillMainBundle/Notification/NotificationPresence.php b/src/Bundle/ChillMainBundle/Notification/NotificationPresence.php index c09b845d7..a6e02c4ea 100644 --- a/src/Bundle/ChillMainBundle/Notification/NotificationPresence.php +++ b/src/Bundle/ChillMainBundle/Notification/NotificationPresence.php @@ -34,9 +34,13 @@ class NotificationPresence $this->notificationRepository = $notificationRepository; } - public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array + /** + * @param list $more + * @return array{unread: int, sent: int, total: int} + */ + public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): array { - if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) { + if ([] === $more && array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) { return $this->cache[$relatedEntityClass][$relatedEntityId]; } @@ -46,21 +50,25 @@ class NotificationPresence $counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated( $relatedEntityClass, $relatedEntityId, - $user + $user, + $more ); - $this->cache[$relatedEntityClass][$relatedEntityId] = $counter; + if ([] === $more) { + $this->cache[$relatedEntityClass][$relatedEntityId] = $counter; + } return $counter; } - return ['unread' => 0, 'read' => 0]; + return ['unread' => 0, 'sent' => 0, 'total' => 0]; } /** + * @param list $more * @return array|Notification[] */ - public function getNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array + public function getNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId, array $more = []): array { $user = $this->security->getUser(); @@ -68,7 +76,8 @@ class NotificationPresence return $this->notificationRepository->findNotificationByRelatedEntityAndUserAssociated( $relatedEntityClass, $relatedEntityId, - $user + $user, + $more ); } diff --git a/src/Bundle/ChillMainBundle/Notification/Templating/NotificationTwigExtensionRuntime.php b/src/Bundle/ChillMainBundle/Notification/Templating/NotificationTwigExtensionRuntime.php index 0720c6da6..a8751374e 100644 --- a/src/Bundle/ChillMainBundle/Notification/Templating/NotificationTwigExtensionRuntime.php +++ b/src/Bundle/ChillMainBundle/Notification/Templating/NotificationTwigExtensionRuntime.php @@ -34,24 +34,30 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface $this->urlGenerator = $urlGenerator; } - public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string + public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): string { return $environment->render( '@ChillMain/Notification/extension_counter_notifications_for.html.twig', [ - 'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId), + 'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more), ] ); } - public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array + /** + * @param list $more + */ + public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): array { - return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId); + return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more); } - public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string + /** + * @param list $more + */ + public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): string { - $notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId); + $notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more); if ([] === $notifications) { return ''; diff --git a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php index 72f9ea5d1..fe42578d9 100644 --- a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php @@ -29,6 +29,15 @@ final class NotificationRepository implements ObjectRepository private EntityRepository $repository; + private const BASE_COUNTER_SQL = <<<'SQL' + SELECT + SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread, + SUM((cmn.sender_id = :userid)::int) AS sent, + SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_user cmnau_all WHERE user_id = :userid and cmnau_all.notification_id = cmn.id))::int) + SUM((cmn.sender_id = :userid)::int) AS total + FROM chill_main_notification cmn + SQL; + + public function __construct(EntityManagerInterface $entityManager) { $this->em = $entityManager; @@ -51,29 +60,45 @@ final class NotificationRepository implements ObjectRepository ->getSingleScalarResult(); } - public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array + /** + * @param list $more + * @return array{unread: int, sent: int, total: int} + */ + public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): array { - if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) { - $sql = - 'SELECT - SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread, - SUM((cmn.sender_id = :userid)::int) AS sent, - COUNT(cmn.*) AS total - FROM chill_main_notification cmn - WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL'; + $sqlParams = ['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]; - $this->notificationByRelatedEntityAndUserAssociatedStatement = - $this->em->getConnection()->prepare($sql); + if ([] === $more) { + if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) { + $sql = self::BASE_COUNTER_SQL . ' WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL'; + + $this->notificationByRelatedEntityAndUserAssociatedStatement = + $this->em->getConnection()->prepare($sql); + } + + $results = $this->notificationByRelatedEntityAndUserAssociatedStatement + ->executeQuery($sqlParams); + + $result = $results->fetchAssociative(); + + $results->free(); + } else { + $wheres = []; + foreach ([ + ['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId], + ...$more + ] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) { + $wheres[] = "(relatedEntityClass = :relatedEntityClass_{$k} AND relatedEntityId = :relatedEntityId_{$k})"; + $sqlParams["relatedEntityClass_{$k}"] = $relClass; + $sqlParams["relatedEntityId_{$k}"] = $relId; + } + + $sql = self::BASE_COUNTER_SQL . ' WHERE sender_id IS NOT NULL AND (' . implode(' OR ', $wheres) . ')'; + + $result = $this->em->getConnection()->fetchAssociative($sql, $sqlParams); } - $results = $this->notificationByRelatedEntityAndUserAssociatedStatement - ->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]); - - $result = $results->fetchAssociative(); - - $results->free(); - - return $result; + return array_map(fn (?int $number) => $number ?? 0, $result); } public function countUnreadByUser(User $user): int @@ -167,8 +192,8 @@ final class NotificationRepository implements ObjectRepository } /** - * @param mixed|null $limit - * @param mixed|null $offset + * @param int|null $limit + * @param int|null $offset * * @return Notification[] */ @@ -178,13 +203,15 @@ final class NotificationRepository implements ObjectRepository } /** + * @param list $more * @return array|Notification[] */ - public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array + public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more): array { return - $this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user) + $this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user, $more) ->select('n') + ->addOrderBy('n.date', 'DESC') ->getQuery() ->getResult(); } @@ -222,13 +249,36 @@ final class NotificationRepository implements ObjectRepository return Notification::class; } - private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder + /** + * @param list $more + */ + private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): QueryBuilder { $qb = $this->repository->createQueryBuilder('n'); + // add condition for related entity (in main arguments, and in more) + $or = $qb->expr()->orX($qb->expr()->andX( + $qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'), + $qb->expr()->eq('n.relatedEntityId', ':relatedEntityId') + )); $qb - ->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass')) - ->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')) + ->setParameter('relatedEntityClass', $relatedEntityClass) + ->setParameter('relatedEntityId', $relatedEntityId); + + foreach ($more as $k => ['relatedEntityClass' => $relatedClass, 'relatedEntityId' => $relatedId]) { + $or->add( + $qb->expr()->andX( + $qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass_'.$k), + $qb->expr()->eq('n.relatedEntityId', ':relatedEntityId_'.$k) + ) + ); + $qb + ->setParameter('relatedEntityClass_'.$k, $relatedClass) + ->setParameter('relatedEntityId_'.$k, $relatedId); + } + + $qb + ->andWhere($or) ->andWhere($qb->expr()->isNotNull('n.sender')) ->andWhere( $qb->expr()->orX( @@ -236,8 +286,6 @@ final class NotificationRepository implements ObjectRepository $qb->expr()->eq('n.sender', ':user') ) ) - ->setParameter('relatedEntityClass', $relatedEntityClass) - ->setParameter('relatedEntityId', $relatedEntityId) ->setParameter('user', $user); return $qb; diff --git a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php index a28f2f341..c115d5d98 100644 --- a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php @@ -14,6 +14,8 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\Regroupment; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\Persistence\ObjectRepository; final class RegroupmentRepository implements ObjectRepository @@ -59,6 +61,30 @@ final class RegroupmentRepository implements ObjectRepository return $this->repository->findOneBy($criteria, $orderBy); } + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function findOneByName(string $name): ?Regroupment + { + return $this->repository->createQueryBuilder('r') + ->where('LOWER(r.name) = LOWER(:searched)') + ->setParameter('searched', $name) + ->getQuery() + ->getSingleResult(); + } + + /** + * @return array + */ + public function findRegroupmentAssociatedToNoCenter(): array + { + return $this->repository->createQueryBuilder('r') + ->where('SIZE(r.centers) = 0') + ->getQuery() + ->getResult(); + } + public function getClassName() { return Regroupment::class; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts new file mode 100644 index 000000000..ce0671d82 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts @@ -0,0 +1,20 @@ +const buildLinkCreate = function (relatedEntityClass: string, relatedEntityId: number, to: number | null, returnPath: string | null): string +{ + const params = new URLSearchParams(); + params.append('entityClass', relatedEntityClass); + params.append('entityId', relatedEntityId.toString()); + + if (null !== to) { + params.append('tos[0]', to.toString()); + } + + if (null !== returnPath) { + params.append('returnPath', returnPath); + } + + return `/fr/notification/create?${params.toString()}`; +} + +export { + buildLinkCreate, +} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/extension_counter_notifications_for.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/extension_counter_notifications_for.html.twig index d92e4230e..0e64e9f6a 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/extension_counter_notifications_for.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/extension_counter_notifications_for.html.twig @@ -9,4 +9,4 @@ {{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }} {% endif %} -
\ No newline at end of file + diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php index 4fe877a72..ae70d55f9 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php @@ -16,21 +16,19 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use function count; -class SocialWorkSocialActionApiController extends ApiController +final class SocialWorkSocialActionApiController extends ApiController { - private PaginatorFactory $paginator; - - private SocialIssueRepository $socialIssueRepository; - - public function __construct(SocialIssueRepository $socialIssueRepository, PaginatorFactory $paginator) - { - $this->socialIssueRepository = $socialIssueRepository; - $this->paginator = $paginator; + public function __construct( + private readonly SocialIssueRepository $socialIssueRepository, + private readonly PaginatorFactory $paginator, + private readonly ClockInterface $clock, + ) { } public function listBySocialIssueApi($id, Request $request) @@ -42,7 +40,10 @@ class SocialWorkSocialActionApiController extends ApiController throw $this->createNotFoundException('socialIssue not found'); } - $socialActions = $socialIssue->getRecursiveSocialActions()->toArray(); + $socialActions = SocialAction::filterRemoveDeactivatedActions( + $socialIssue->getRecursiveSocialActions()->toArray(), + \DateTime::createFromImmutable($this->clock->now()) + ); usort($socialActions, static fn (SocialAction $sa, SocialAction $sb) => $sa->getOrdering() <=> $sb->getOrdering()); diff --git a/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php index 658fdb7be..1b67014b3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php +++ b/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php @@ -12,9 +12,11 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class UserAccompanyingPeriodController extends AbstractController @@ -32,12 +34,24 @@ class UserAccompanyingPeriodController extends AbstractController /** * @Route("/{_locale}/person/accompanying-periods/my", name="chill_person_accompanying_period_user") */ - public function listAction(Request $request) + public function listAction(Request $request): Response { - $total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']]); + $active = $request->query->getBoolean('active', true); + $steps = match ($active) { + true => [ + AccompanyingPeriod::STEP_CONFIRMED, + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, + ], + false => [ + AccompanyingPeriod::STEP_CLOSED, + ] + }; + + $total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => $steps]); $pagination = $this->paginatorFactory->create($total); $accompanyingPeriods = $this->accompanyingPeriodRepository->findBy( - ['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']], + ['user' => $this->getUser(), 'step' => $steps], ['openingDate' => 'DESC'], $pagination->getItemsPerPage(), $pagination->getCurrentPageFirstItemNumber() @@ -46,13 +60,14 @@ class UserAccompanyingPeriodController extends AbstractController return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [ 'accompanyingPeriods' => $accompanyingPeriods, 'pagination' => $pagination, + 'active' => $active, ]); } /** * @Route("/{_locale}/person/accompanying-periods/my/drafts", name="chill_person_accompanying_period_draft_user") */ - public function listDraftsAction(Request $request) + public function listDraftsAction(): Response { $total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => 'DRAFT']); $pagination = $this->paginatorFactory->create($total); diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index a8e191df3..accf39db5 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -185,7 +185,9 @@ class AccompanyingPeriod implements * cascade={"persist", "remove"}, * orphanRemoval=true * ) + * @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"}) * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT}) + * @var Collection */ private Collection $comments; @@ -705,10 +707,11 @@ class AccompanyingPeriod implements ->comments ->filter( static fn (Comment $c): bool => $c !== $pinnedComment - ); + ) + ; } - public function getCreatedAt(): ?DateTime + public function getCreatedAt(): DateTimeInterface { return $this->createdAt; } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 5361012b3..e3b2883be 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -59,6 +59,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues * ) * @Serializer\Groups({"read", "docgen:read"}) * @ORM\OrderBy({"startDate": "DESC", "id": "DESC"}) + * @var Collection * * @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` */ @@ -278,6 +279,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues return $this->accompanyingPeriod; } + /** + * @return Collection + */ public function getAccompanyingPeriodWorkEvaluations(): Collection { return $this->accompanyingPeriodWorkEvaluations; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php index 8780f7d17..d35ffc900 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php @@ -79,6 +79,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * ) * @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"}) * @Serializer\Groups({"read"}) + * @var Collection */ private Collection $documents; @@ -204,7 +205,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU } /** - * @return Collection + * @return Collection */ public function getDocuments() { diff --git a/src/Bundle/ChillPersonBundle/Entity/Person/PersonCenterCurrent.php b/src/Bundle/ChillPersonBundle/Entity/Person/PersonCenterCurrent.php index 6d67fbbbb..6de73de0d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person/PersonCenterCurrent.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person/PersonCenterCurrent.php @@ -45,7 +45,7 @@ class PersonCenterCurrent private ?int $id = null; /** - * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="centerCurrent") + * @ORM\OneToOne(targetEntity=Person::class, inversedBy="centerCurrent") */ private Person $person; diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index a8c68c8ce..b5b0f9101 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -15,6 +15,7 @@ use DateInterval; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ReadableCollection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation as Serializer; @@ -295,6 +296,19 @@ class SocialAction return 0 < $this->getChildren()->count(); } + public function isDesactivated(\DateTime $atDate): bool + { + if (null !== $this->desactivationDate && $this->desactivationDate < $atDate) { + return true; + } + + if ($this->hasParent()) { + return $this->parent->isDesactivated($atDate); + } + + return false; + } + public function hasParent(): bool { return $this->getParent() instanceof self; @@ -401,4 +415,14 @@ class SocialAction return $this; } + + public static function filterRemoveDeactivatedActions(ReadableCollection|array $actions, \DateTime $comparisonDate): ReadableCollection|array + { + $filterFn = fn (SocialAction $socialAction) => !$socialAction->isDesactivated($comparisonDate); + + return match ($actions instanceof ReadableCollection) { + true => $actions->filter($filterFn), + false => array_filter($actions, $filterFn) + }; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index 675a1a923..f12627ae3 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -253,7 +253,7 @@ class SocialIssue } /** - * @return Collection|SocialAction[] All the descendant social actions of all + * @return Collection All the descendant social actions of all * the descendants of the entity */ public function getRecursiveSocialActions(): Collection @@ -272,7 +272,7 @@ class SocialIssue } /** - * @return Collection|SocialAction[] + * @return Collection */ public function getSocialActions(): Collection { diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php new file mode 100644 index 000000000..9033c64b3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php @@ -0,0 +1,125 @@ + AccompanyingPeriod::STEP_DRAFT, + 'course.confirmed' => AccompanyingPeriod::STEP_CONFIRMED, + 'course.closed' => AccompanyingPeriod::STEP_CLOSED, + 'course.inactive_short' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, + 'course.inactive_long' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, + ]; + + private RollingDateConverterInterface $rollingDateConverter; + + private TranslatorInterface $translator; + + public function __construct( + RollingDateConverterInterface $rollingDateConverter, + TranslatorInterface $translator + ) { + $this->rollingDateConverter = $rollingDateConverter; + $this->translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $alias = 'acp_filter_by_step_between_dat_alias'; + $steps = 'acp_filter_by_step_between_dat_steps'; + $from = 'acp_filter_by_step_between_dat_from'; + $to = 'acp_filter_by_step_between_dat_to'; + + $qb + ->andWhere( + $qb->expr()->exists( + "SELECT 1 FROM " . AccompanyingPeriod\AccompanyingPeriodStepHistory::class . " {$alias} " . + "WHERE {$alias}.step IN (:{$steps}) AND OVERLAPSI ({$alias}.startDate, {$alias}.endDate),(:{$from}, :{$to}) = TRUE " . + "AND {$alias}.period = acp" + ) + ) + ->setParameter($from, $this->rollingDateConverter->convert($data['date_from'])) + ->setParameter($to, $this->rollingDateConverter->convert($data['date_to'])) + ->setParameter($steps, $data['accepted_steps_multi']); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('accepted_steps_multi', ChoiceType::class, [ + 'label' => 'export.filter.course.by_step.steps', + 'choices' => self::STEPS, + 'multiple' => true, + 'expanded' => true, + ]) + ->add('date_from', PickRollingDateType::class, [ + 'label' => 'export.filter.course.by_step.date_from', + ]) + ->add('date_to', PickRollingDateType::class, [ + 'label' => 'export.filter.course.by_step.date_to', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'accepted_steps_multi' => self::DEFAULT_CHOICE, + 'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'date_to' => new RollingDate(RollingDate::T_TODAY), + ]; + } + + public function describeAction($data, $format = 'string') + { + $steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']); + + return ['export.filter.course.by_step.Filtered by steps: only %step% and between %date_from% and %date_to%', [ + '%step%' => implode(', ', $steps), + '%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), + '%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'), + ]]; + } + + public function getTitle() + { + return 'export.filter.course.by_step.Filter by step between dates'; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php similarity index 79% rename from src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php rename to src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php index 0a816c0b6..357ed6f30 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php @@ -23,11 +23,15 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function in_array; -class StepFilter implements FilterInterface +class StepFilterOnDate implements FilterInterface { private const A = 'acp_filter_bystep_stephistories'; - private const DEFAULT_CHOICE = AccompanyingPeriod::STEP_CONFIRMED; + private const DEFAULT_CHOICE = [ + AccompanyingPeriod::STEP_CONFIRMED, + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, + ]; private const P = 'acp_step_filter_date'; @@ -79,7 +83,7 @@ class StepFilter implements FilterInterface $qb->expr()->in(self::A . '.step', ':acp_filter_by_step_steps') ) ->setParameter(self::P, $this->rollingDateConverter->convert($data['calc_date'])) - ->setParameter('acp_filter_by_step_steps', $data['accepted_steps']); + ->setParameter('acp_filter_by_step_steps', $data['accepted_steps_multi']); } public function applyOn() @@ -90,32 +94,36 @@ class StepFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { $builder - ->add('accepted_steps', ChoiceType::class, [ + ->add('accepted_steps_multi', ChoiceType::class, [ + 'label' => 'export.filter.course.by_step.steps', 'choices' => self::STEPS, - 'multiple' => false, + 'multiple' => true, 'expanded' => true, - 'empty_data' => self::DEFAULT_CHOICE, ]) ->add('calc_date', PickRollingDateType::class, [ 'label' => 'export.filter.course.by_step.date_calc', ]); } + public function getFormDefaultData(): array { - return ['accepted_steps' => self::DEFAULT_CHOICE, 'calc_date' => new RollingDate(RollingDate::T_TODAY)]; + return [ + 'accepted_steps_multi' => self::DEFAULT_CHOICE, + 'calc_date' => new RollingDate(RollingDate::T_TODAY), + ]; } public function describeAction($data, $format = 'string') { - $step = array_flip(self::STEPS)[$data['accepted_steps']]; + $steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']); return ['Filtered by steps: only %step%', [ - '%step%' => $this->translator->trans($step), + '%step%' => implode(', ', $steps), ]]; } public function getTitle() { - return 'Filter by step'; + return 'export.filter.course.by_step.Filter by step'; } } diff --git a/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php index 074c27027..13291bdd1 100644 --- a/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/HouseholdMenuBuilder.php @@ -12,7 +12,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Menu; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Knp\Menu\MenuItem; +use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; class HouseholdMenuBuilder implements LocalMenuBuilderInterface @@ -22,9 +24,12 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface */ protected $translator; - public function __construct(TranslatorInterface $translator) + private Security $security; + + public function __construct(TranslatorInterface $translator, Security $security) { $this->translator = $translator; + $this->security = $security; } public function buildMenu($menuId, MenuItem $menu, array $parameters): void @@ -53,12 +58,14 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface ], ]) ->setExtras(['order' => 17]); - $menu->addChild($this->translator->trans('household.Accompanying period'), [ - 'route' => 'chill_person_household_accompanying_period', - 'routeParameters' => [ - 'household_id' => $household->getId(), - ], ]) - ->setExtras(['order' => 20]); + if ($this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['household'])) { + $menu->addChild($this->translator->trans('household.Accompanying period'), [ + 'route' => 'chill_person_household_accompanying_period', + 'routeParameters' => [ + 'household_id' => $household->getId(), + ],]) + ->setExtras(['order' => 20]); + } $menu->addChild($this->translator->trans('household.Addresses'), [ 'route' => 'chill_person_household_addresses', diff --git a/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationHandler.php b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationHandler.php index 486209453..2492e9a41 100644 --- a/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationHandler.php +++ b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodNotificationHandler.php @@ -27,7 +27,7 @@ final class AccompanyingPeriodNotificationHandler implements NotificationHandler public function getTemplate(Notification $notification, array $options = []): string { - return 'ChillPersonBundle:AccompanyingPeriod:showInNotification.html.twig'; + return '@ChillPerson/AccompanyingPeriod/showInNotification.html.twig'; } public function getTemplateData(Notification $notification, array $options = []): array diff --git a/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodWorkEvaluationDocumentNotificationHandler.php b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodWorkEvaluationDocumentNotificationHandler.php new file mode 100644 index 000000000..4dcb03489 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodWorkEvaluationDocumentNotificationHandler.php @@ -0,0 +1,47 @@ +accompanyingPeriodWorkEvaluationDocumentRepository = $accompanyingPeriodWorkEvaluationDocumentRepository; + } + + public function getTemplate(Notification $notification, array $options = []): string + { + return '@ChillPerson/AccompanyingCourseWork/showEvaluationDocumentInNotification.html.twig'; + } + + public function getTemplateData(Notification $notification, array $options = []): array + { + return [ + 'notification' => $notification, + 'document' => $doc = $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($notification->getRelatedEntityId()), + 'evaluation' => $doc?->getAccompanyingPeriodWorkEvaluation(), + ]; + } + + public function supports(Notification $notification, array $options = []): bool + { + return $notification->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluationDocument::class; + } +} diff --git a/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodWorkNotificationHandler.php b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodWorkNotificationHandler.php new file mode 100644 index 000000000..b111c131f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Notification/AccompanyingPeriodWorkNotificationHandler.php @@ -0,0 +1,46 @@ +accompanyingPeriodWorkRepository = $accompanyingPeriodWorkRepository; + } + + public function getTemplate(Notification $notification, array $options = []): string + { + return '@ChillPerson/AccompanyingCourseWork/showInNotification.html.twig'; + } + + public function getTemplateData(Notification $notification, array $options = []): array + { + return [ + 'notification' => $notification, + 'work' => $this->accompanyingPeriodWorkRepository->find($notification->getRelatedEntityId()), + ]; + } + + public function supports(Notification $notification, array $options = []): bool + { + return $notification->getRelatedEntityClass() === AccompanyingPeriodWork::class; + } +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index 755e4455c..ad386992e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -122,7 +122,8 @@ + v-bind:evaluation="e" + v-bind:docAnchorId="this.docAnchorId"> @@ -296,7 +297,21 @@ @go-to-generate-workflow="goToGenerateWorkflow" > - + +
  • + + +
  • +
  • +
  • +
  • + + +
  • @@ -187,6 +201,7 @@ import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload import AddAsyncUploadDownloader from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUploadDownloader.vue'; import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue'; import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js'; +import {buildLinkCreate as buildLinkCreateNotification} from 'ChillMainAssets/lib/entity-notification/api'; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; const i18n = { @@ -214,14 +229,17 @@ const i18n = { template_title: "Nom du template", browse: "Ajouter un document", replace: "Remplacer", - download: "Télécharger le fichier existant" + download: "Télécharger le fichier existant", + notification_notify_referrer: "Notifier le référent", + notification_notify_any: "Notifier d'autres utilisateurs", + notification_send: "Envoyer une notification", } } }; export default { name: "FormEvaluation", - props: ['evaluation'], + props: ['evaluation', 'docAnchorId'], components: { ckeditor: CKEditor.component, PickTemplate, @@ -260,8 +278,14 @@ export default { }, computed: { ...mapState([ - 'isPosting' + 'isPosting', + 'work', + 'me', ]), + AmIRefferer() { + return (!(this.$store.state.work.accompanyingPeriod.user && this.$store.state.me + && (this.$store.state.work.accompanyingPeriod.user.id !== this.$store.state.me.id))); + }, getTemplatesAvailables() { return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); }, @@ -390,6 +414,20 @@ export default { return this.$store.dispatch('submit', callback) .catch(e => { console.log(e); throw e; }); }, + goToGenerateDocumentNotification(document, tos) { + const callback = (data) => { + let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); + let updatedDocument = evaluation.documents.find(d => d.key === document.key); + window.location.assign(buildLinkCreateNotification( + 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', + updatedDocument.id, + tos === true ? this.$store.state.work.accompanyingPeriod.user.id : null, + window.location.pathname + window.location.search + window.location.hash + )); + }; + return this.$store.dispatch('submit', callback) + .catch(e => {console.log(e); throw e}); + } }, } @@ -402,4 +440,19 @@ export default { ul.document-upload { justify-content: flex-start; } + + .bg-blink{ + color: #050000; + padding: 10px; + display: inline-block; + border-radius: 5px; + animation: blinkingBackground 2.2s infinite; + animation-iteration-count: 2; + } + + @keyframes blinkingBackground{ + 0% { background-color: #ed776d;} + 50% { background-color: #ffffff;} + 100% { background-color: #ed776d;} + } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 47e4b2d3f..9a46b741a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -35,6 +35,7 @@ const store = createStore({ referrers: window.accompanyingCourseWork.referrers, isPosting: false, errors: [], + me: null }, getters: { socialAction(state) { @@ -130,6 +131,9 @@ const store = createStore({ } }, mutations: { + setWhoAmiI(state, me) { + state.me = me; + }, setEvaluationsPicked(state, evaluations) { state.evaluationsPicked = evaluations.map((e, index) => { var k = Object.assign(e, { @@ -385,6 +389,19 @@ const store = createStore({ }, }, actions: { + getWhoAmI({ commit }) { + let url = `/api/1.0/main/whoami.json`; + window.fetch(url) + .then(response => { + if (response.ok) { + return response.json(); + } + throw { m: 'Error while retriving results for goal', s: response.status, b: response.body }; + }) + .then(data => { + commit('setWhoAmiI', data); + }); + }, updateThirdParty({ commit }, payload) { commit('updateThirdParty', payload); }, @@ -514,6 +531,7 @@ store.commit('setEvaluationsPicked', window.accompanyingCourseWork.accompanyingP store.dispatch('getReachablesResultsForAction'); store.dispatch('getReachablesGoalsForAction'); store.dispatch('getReachablesEvaluationsForAction'); +store.dispatch('getWhoAmI'); store.state.evaluationsPicked.forEach(evaluation => { store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue index 740eb0039..2576efd35 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue @@ -207,7 +207,7 @@