Merge branch '709-notification-eval-action' into 'master'

[Feature] add notification to social work and document in social work's evaluations

Closes champs-libres/departement-de-la-vendee/chill#709

See merge request Chill-Projet/chill-bundles!509
This commit is contained in:
Julien Fastré 2023-06-16 12:02:30 +00:00
commit bf2a4bafd8
28 changed files with 818 additions and 171 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: Add notification to accompanying period work and work's evaluation's documents
time: 2023-06-13T23:06:40.090777525+02:00
custom:
Issue: ""

View File

@ -0,0 +1,5 @@
kind: Fixed
body: Fix the notification counter
time: 2023-06-13T23:08:38.67342897+02:00
custom:
Issue: "55"

View File

@ -44,3 +44,9 @@ async function download_and_open(event: Event): Promise<void> {
} }
</script> </script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -88,3 +88,9 @@ async function download_and_open(event: Event): Promise<void> {
console.log('open button should have been clicked'); console.log('open button should have been clicked');
} }
</script> </script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -40,5 +40,8 @@ async function beforeLeave(event: Event): Promise<true> {
</script> </script>
<style scoped lang="sass"> <style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style> </style>

View File

@ -1,5 +1,5 @@
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} {%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<div <div class="d-inline-flex"
data-download-buttons data-download-buttons
data-stored-object="{{ document_json|json_encode|escape('html_attr') }}" data-stored-object="{{ document_json|json_encode|escape('html_attr') }}"
data-can-edit="{{ can_edit ? '1' : '0' }}" data-can-edit="{{ can_edit ? '1' : '0' }}"

View File

@ -18,6 +18,7 @@ No document found: Aucun document trouvé
The document is successfully registered: Le document est enregistré The document is successfully registered: Le document est enregistré
The document is successfully updated: Le document est mis à jour The document is successfully updated: Le document est mis à jour
Any description: Aucune description Any description: Aucune description
See the document: Voir le document
document: document:
Any title: Aucun titre Any title: Aucun titre

View File

@ -34,9 +34,13 @@ class NotificationPresence
$this->notificationRepository = $notificationRepository; $this->notificationRepository = $notificationRepository;
} }
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $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]; return $this->cache[$relatedEntityClass][$relatedEntityId];
} }
@ -46,21 +50,25 @@ class NotificationPresence
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated( $counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass, $relatedEntityClass,
$relatedEntityId, $relatedEntityId,
$user $user,
$more
); );
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter; if ([] === $more) {
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
}
return $counter; return $counter;
} }
return ['unread' => 0, 'read' => 0]; return ['unread' => 0, 'sent' => 0, 'total' => 0];
} }
/** /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array|Notification[] * @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(); $user = $this->security->getUser();
@ -68,7 +76,8 @@ class NotificationPresence
return $this->notificationRepository->findNotificationByRelatedEntityAndUserAssociated( return $this->notificationRepository->findNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass, $relatedEntityClass,
$relatedEntityId, $relatedEntityId,
$user $user,
$more
); );
} }

View File

@ -34,24 +34,30 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
$this->urlGenerator = $urlGenerator; $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( return $environment->render(
'@ChillMain/Notification/extension_counter_notifications_for.html.twig', '@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<array{relatedEntityClass: class-string, relatedEntityId: int}> $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<array{relatedEntityClass: class-string, relatedEntityId: int}> $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) { if ([] === $notifications) {
return ''; return '';

View File

@ -29,6 +29,15 @@ final class NotificationRepository implements ObjectRepository
private EntityRepository $repository; 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) public function __construct(EntityManagerInterface $entityManager)
{ {
$this->em = $entityManager; $this->em = $entityManager;
@ -51,29 +60,45 @@ final class NotificationRepository implements ObjectRepository
->getSingleScalarResult(); ->getSingleScalarResult();
} }
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $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) { $sqlParams = ['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()];
$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';
$this->notificationByRelatedEntityAndUserAssociatedStatement = if ([] === $more) {
$this->em->getConnection()->prepare($sql); 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 return array_map(fn (?int $number) => $number ?? 0, $result);
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]);
$result = $results->fetchAssociative();
$results->free();
return $result;
} }
public function countUnreadByUser(User $user): int public function countUnreadByUser(User $user): int
@ -167,8 +192,8 @@ final class NotificationRepository implements ObjectRepository
} }
/** /**
* @param mixed|null $limit * @param int|null $limit
* @param mixed|null $offset * @param int|null $offset
* *
* @return Notification[] * @return Notification[]
*/ */
@ -178,13 +203,15 @@ final class NotificationRepository implements ObjectRepository
} }
/** /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array|Notification[] * @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 return
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user) $this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user, $more)
->select('n') ->select('n')
->addOrderBy('n.date', 'DESC')
->getQuery() ->getQuery()
->getResult(); ->getResult();
} }
@ -222,13 +249,36 @@ final class NotificationRepository implements ObjectRepository
return Notification::class; return Notification::class;
} }
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder /**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): QueryBuilder
{ {
$qb = $this->repository->createQueryBuilder('n'); $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 $qb
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass')) ->setParameter('relatedEntityClass', $relatedEntityClass)
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')) ->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()->isNotNull('n.sender'))
->andWhere( ->andWhere(
$qb->expr()->orX( $qb->expr()->orX(
@ -236,8 +286,6 @@ final class NotificationRepository implements ObjectRepository
$qb->expr()->eq('n.sender', ':user') $qb->expr()->eq('n.sender', ':user')
) )
) )
->setParameter('relatedEntityClass', $relatedEntityClass)
->setParameter('relatedEntityId', $relatedEntityId)
->setParameter('user', $user); ->setParameter('user', $user);
return $qb; return $qb;

View File

@ -9,4 +9,4 @@
{{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }} {{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }}
</span> </span>
{% endif %} {% endif %}
</div> </div>

View File

@ -59,6 +59,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* ) * )
* @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"read", "docgen:read"})
* @ORM\OrderBy({"startDate": "DESC", "id": "DESC"}) * @ORM\OrderBy({"startDate": "DESC", "id": "DESC"})
* @var Collection<AccompanyingPeriodWorkEvaluation>
* *
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` * @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
*/ */
@ -278,6 +279,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this->accompanyingPeriod; return $this->accompanyingPeriod;
} }
/**
* @return Collection<AccompanyingPeriodWorkEvaluation>
*/
public function getAccompanyingPeriodWorkEvaluations(): Collection public function getAccompanyingPeriodWorkEvaluations(): Collection
{ {
return $this->accompanyingPeriodWorkEvaluations; return $this->accompanyingPeriodWorkEvaluations;

View File

@ -79,6 +79,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* ) * )
* @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"}) * @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"})
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
* @var Collection<AccompanyingPeriodWorkEvaluationDocument>
*/ */
private Collection $documents; private Collection $documents;
@ -204,7 +205,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
} }
/** /**
* @return Collection * @return Collection<AccompanyingPeriodWorkEvaluationDocument>
*/ */
public function getDocuments() public function getDocuments()
{ {

View File

@ -27,7 +27,7 @@ final class AccompanyingPeriodNotificationHandler implements NotificationHandler
public function getTemplate(Notification $notification, array $options = []): string 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 public function getTemplateData(Notification $notification, array $options = []): array

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
final class AccompanyingPeriodWorkEvaluationDocumentNotificationHandler implements NotificationHandlerInterface
{
private AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository;
public function __construct(AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository)
{
$this->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;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
final class AccompanyingPeriodWorkNotificationHandler implements NotificationHandlerInterface
{
private AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository;
public function __construct(AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository)
{
$this->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;
}
}

View File

@ -297,7 +297,21 @@
@go-to-generate-workflow="goToGenerateWorkflow" @go-to-generate-workflow="goToGenerateWorkflow"
></list-workflow-modal> ></list-workflow-modal>
</li> </li>
<li>
<button v-if="AmIRefferer"
class="btn btn-notify"
@click="goToGenerateNotification(false)"
></button>
<template v-else>
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" :title="$t('notification_send')" data-bs-toggle="dropdown" aria-expanded="false">&nbsp;</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li><a class="dropdown-item" @click="goToGenerateNotification(true)">{{ $t('notification_notify_referrer') }}</a></li>
<li><a class="dropdown-item" @click="goToGenerateNotification(false)">{{ $t('notification_notify_any') }}</a></li>
</ul>
</template>
</li>
<li v-if="!isPosting"> <li v-if="!isPosting">
<button class="btn btn-save" @click="submit"> <button class="btn btn-save" @click="submit">
{{ $t('action.save') }} {{ $t('action.save') }}
@ -367,7 +381,10 @@ const i18n = {
no_referrers: "Aucun agent traitant", no_referrers: "Aucun agent traitant",
choose_referrers: "Choisir des agents traitants", choose_referrers: "Choisir des agents traitants",
remove_referrer: "Enlever l'agent", remove_referrer: "Enlever l'agent",
private_comment: "Commentaire privé" private_comment: "Commentaire privé",
notification_notify_referrer: "Notifier le référent",
notification_notify_any: "Notifier d'autres utilisateurs",
notification_send: "Envoyer une notification",
} }
} }
}; };
@ -450,6 +467,7 @@ export default {
'isPosting', 'isPosting',
'errors', 'errors',
'templatesAvailablesForAction', 'templatesAvailablesForAction',
'me',
]), ]),
...mapGetters([ ...mapGetters([
'hasResultsForAction', 'hasResultsForAction',
@ -507,6 +525,10 @@ export default {
this.$store.commit('setPersonsPickedIds', v); this.$store.commit('setPersonsPickedIds', v);
} }
}, },
AmIRefferer() {
return (!(this.work.accompanyingPeriod.user && this.me
&& (this.work.accompanyingPeriod.user.id !== this.me.id)));
}
}, },
methods: { methods: {
toggleSelect() { toggleSelect() {
@ -557,6 +579,19 @@ export default {
return this.$store.dispatch('submit', callback) return this.$store.dispatch('submit', callback)
.catch(e => { console.log(e); throw e; }); .catch(e => { console.log(e); throw e; });
}, },
goToGenerateNotification(tos) {
console.log('save before leave to notification');
const callback = (data) => {
if (tos === true) {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork&entityId=${this.work.id}&tos[0]=${this.work.accompanyingPeriod.user.id}&returnPath=/fr/person/accompanying-period/${this.work.accompanyingPeriod.id}/work`);
} else {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork&entityId=${this.work.id}&returnPath=/fr/person/accompanying-period/${this.work.accompanyingPeriod.id}/work`);
}
}
return this.$store.dispatch('submit', callback)
.catch(e => {console.log(e); throw e});
},
submit() { submit() {
this.$store.dispatch('submit').catch((error) => { this.$store.dispatch('submit').catch((error) => {
if (error.name === 'ValidationException' || error.name === 'AccessException') { if (error.name === 'ValidationException' || error.name === 'AccessException') {

View File

@ -102,27 +102,32 @@
</div> </div>
<div class="item-row"> <div class="item-row">
<div class="item-col"> <div class="item-col">
<ul class="record_actions" > <ul class="record_actions">
<li v-if="d.workflows_availables.length > 0"> <li v-if="d.workflows_availables.length > 0">
<list-workflow-modal <list-workflow-modal
:workflows="d.workflows" :workflows="d.workflows"
:allowCreate="true" :allowCreate="true"
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument" relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
:relatedEntityId="d.id" :relatedEntityId="d.id"
:workflowsAvailables="d.workflows_availables" :workflowsAvailables="d.workflows_availables"
:preventDefaultMoveToGenerate="true" :preventDefaultMoveToGenerate="true"
:goToGenerateWorkflowPayload="{doc: d}" :goToGenerateWorkflowPayload="{doc: d}"
@go-to-generate-workflow="goToGenerateWorkflowEvaluationDocument" @go-to-generate-workflow="goToGenerateWorkflowEvaluationDocument"
></list-workflow-modal> ></list-workflow-modal>
</li> </li>
<li> <li>
<add-async-upload <button
:buttonTitle="$t('replace')" v-if="AmIRefferer"
:options="asyncUploadOptions" class="btn btn-notify"
:btnClasses="{'btn': true, 'btn-edit': true}" @click="goToGenerateDocumentNotification(d, false)">
@addDocument="(arg) => replaceDocument(d, arg)" </button>
> <template v-else>
</add-async-upload> <button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" :title="$t('notification_send')" data-bs-toggle="dropdown" aria-expanded="false">&nbsp;</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li><a class="dropdown-item" @click="goToGenerateDocumentNotification(d, true)">{{ $t('notification_notify_referrer') }}</a></li>
<li><a class="dropdown-item" @click="goToGenerateDocumentNotification(d, false)">{{ $t('notification_notify_any') }}</a></li>
</ul>
</template>
</li> </li>
<li> <li>
<document-action-buttons-group <document-action-buttons-group
@ -133,6 +138,15 @@
@on-stored-object-status-change="onStatusDocumentChanged" @on-stored-object-status-change="onStatusDocumentChanged"
></document-action-buttons-group> ></document-action-buttons-group>
</li> </li>
<li>
<add-async-upload
:buttonTitle="$t('replace')"
:options="asyncUploadOptions"
:btnClasses="{'btn': true, 'btn-edit': true}"
@addDocument="(arg) => replaceDocument(d, arg)"
>
</add-async-upload>
</li>
<li v-if="d.workflows.length === 0"> <li v-if="d.workflows.length === 0">
<a class="btn btn-delete" @click="removeDocument(d)"> <a class="btn btn-delete" @click="removeDocument(d)">
</a> </a>
@ -214,7 +228,10 @@ const i18n = {
template_title: "Nom du template", template_title: "Nom du template",
browse: "Ajouter un document", browse: "Ajouter un document",
replace: "Remplacer", 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",
} }
} }
}; };
@ -260,8 +277,14 @@ export default {
}, },
computed: { computed: {
...mapState([ ...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() { getTemplatesAvailables() {
return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation);
}, },
@ -390,6 +413,18 @@ export default {
return this.$store.dispatch('submit', callback) return this.$store.dispatch('submit', callback)
.catch(e => { console.log(e); throw e; }); .catch(e => { console.log(e); throw e; });
}, },
goToGenerateDocumentNotification(document, tos){
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key);
if (tos === true) {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument&entityId=${document.id}&tos[0]=${this.$store.state.work.accompanyingPeriod.user.id}&returnPath=/fr/person/accompanying-period/work/${evaluation.id}/edit`)
} else {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument&entityId=${document.id}&returnPath=/fr/person/accompanying-period/work/${evaluation.id}/edit`)
}
};
return this.$store.dispatch('submit', callback)
.catch(e => {console.log(e); throw e});
}
}, },
} }
</script> </script>

View File

@ -35,6 +35,7 @@ const store = createStore({
referrers: window.accompanyingCourseWork.referrers, referrers: window.accompanyingCourseWork.referrers,
isPosting: false, isPosting: false,
errors: [], errors: [],
me: null
}, },
getters: { getters: {
socialAction(state) { socialAction(state) {
@ -130,6 +131,9 @@ const store = createStore({
} }
}, },
mutations: { mutations: {
setWhoAmiI(state, me) {
state.me = me;
},
setEvaluationsPicked(state, evaluations) { setEvaluationsPicked(state, evaluations) {
state.evaluationsPicked = evaluations.map((e, index) => { state.evaluationsPicked = evaluations.map((e, index) => {
var k = Object.assign(e, { var k = Object.assign(e, {
@ -385,6 +389,19 @@ const store = createStore({
}, },
}, },
actions: { 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) { updateThirdParty({ commit }, payload) {
commit('updateThirdParty', payload); commit('updateThirdParty', payload);
}, },
@ -514,6 +531,7 @@ store.commit('setEvaluationsPicked', window.accompanyingCourseWork.accompanyingP
store.dispatch('getReachablesResultsForAction'); store.dispatch('getReachablesResultsForAction');
store.dispatch('getReachablesGoalsForAction'); store.dispatch('getReachablesGoalsForAction');
store.dispatch('getReachablesEvaluationsForAction'); store.dispatch('getReachablesEvaluationsForAction');
store.dispatch('getWhoAmI');
store.state.evaluationsPicked.forEach(evaluation => { store.state.evaluationsPicked.forEach(evaluation => {
store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation) store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation)

View File

@ -5,7 +5,8 @@
# - displayAction: [true|false] default: false # - displayAction: [true|false] default: false
# - displayFontSmall: [true|false] default: false # - displayFontSmall: [true|false] default: false
#} #}
<div class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> <div
class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<div class="item-row"> <div class="item-row">
<h2 class="badge-title"> <h2 class="badge-title">
@ -111,9 +112,11 @@
</div> </div>
{% if displayContent is not defined or displayContent == 'short' %} {% if displayContent is not defined or displayContent == 'short' %}
<div class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}"> <div
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with { class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}">
'displayContent': displayContent {% include '@ChillPerson/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent,
'onlyone': false
} %} } %}
</div> </div>
{% endif %} {% endif %}
@ -121,35 +124,56 @@
{% if displayContent is not defined or displayContent == 'short' %} {% if displayContent is not defined or displayContent == 'short' %}
<div class="item-row separator"> <div class="item-row separator">
{% import '@ChillPerson/AccompanyingCourseWork/_macros.html.twig' as macro %} {% import '@ChillPerson/AccompanyingCourseWork/_macros.html.twig' as macro %}
<div class="item-col item-meta"> <div class="item-col item-meta">
{{ macro.metadata(w) }} {{ macro.metadata(w) }}
</div> </div>
{% if displayAction is defined and displayAction == true %} {% if displayAction is defined and displayAction == true %}
<ul class="item-col record_actions"> <ul class="item-col record_actions">
<li>{{ macro.workflowButton(w) }}</li> <li>{{ macro.workflowButton(w) }}</li>
<li> {% if displayNotification is defined and displayNotification == true %}
<a class="btn btn-show" title="{{ 'Show'|trans }}" <li>
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id }) }}" <div class="d-grid gap-2 {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}btn-group{% endif %}" {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}role="group"{% endif %}>
></a> {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
</li> <button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}" data-bs-toggle="dropdown" aria-expanded="false">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %} </button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify" title="{{ 'notification.Notify'|trans }}" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id}) }}">
</a>
{% endif %}
</div>
</li>
{% endif %}
<li> <li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}" <a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}" href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id }) }}"
></a> ></a>
</li> </li>
{% endif %} {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %} <li>
<li> <a class="btn btn-edit" title="{{ 'Edit'|trans }}"
<a class="btn btn-delete" title="{{ 'Delete'|trans }}" href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}" ></a>
></a> </li>
</li> {% endif %}
{% endif %} {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
</ul> <li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
></a>
</li>
{% endif %}
</ul>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -157,47 +181,51 @@
</div> </div>
{# {#
# This is for 'long' version of content # This is for 'long' version of content
# Note: this include is wrapped in a flex-table container. # Note: this include is wrapped in a flex-table container.
# We start by closing the flex-table so we can add more. # We start by closing the flex-table so we can add more.
# At the end we leave the last flex-table open, as it will be closed in the container. # At the end we leave the last flex-table open, as it will be closed in the container.
#} #}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
</div>
{% if w.results|length > 0 or w.goals|length > 0 or w.accompanyingPeriodWorkEvaluations|length > 0 %}
<h2 class="chill-blue">{{ 'Dispositifs' }}</h2>
<div class="flex-table">{# new flex-table wrapper #}
<div
class="item-bloc colored{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent,
'onlyone' : false
} %}
</div>
</div> </div>
{% endif %}
{% if w.results|length > 0 or w.goals|length > 0 or w.accompanyingPeriodWorkEvaluations|length > 0 %} <h2 class="chill-blue">{{ 'Comments'|trans }}</h2>
<h2 class="chill-blue">{{ 'Dispositifs' }}</h2>
<div class="flex-table">{# new flex-table wrapper #} <div class="flex-table">
<div class="item-bloc colored{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> <div
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with { class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
'displayContent': displayContent <h3 class="chill-beige">Public</h3>
} %} {% if w.note is not empty %}
</div> <blockquote class="chill-user-quote">
{{ w.note|chill_entity_render_box({'metadata': true }) }}
</blockquote>
{% else %}
<span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span>
{% endif %}
</div>
{% if w.privateComment.hasCommentForUser(app.user) %}
<div
class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Privé</h3>
<blockquote class="chill-user-quote private-quote">
{{ w.privateComment.commentForUser(app.user)|chill_markdown_to_html }}
</blockquote>
</div> </div>
{% endif %} {% endif %}
<h2 class="chill-blue">{{ 'Comments'|trans }}</h2>
<div class="flex-table">
<div class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Public</h3>
{% if w.note is not empty %}
<blockquote class="chill-user-quote">
{{ w.note|chill_entity_render_box({'metadata': true }) }}
</blockquote>
{% else %}
<span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span>
{% endif %}
</div>
{% if w.privateComment.hasCommentForUser(app.user) %}
<div class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Privé</h3>
<blockquote class="chill-user-quote private-quote">
{{ w.privateComment.commentForUser(app.user)|chill_markdown_to_html }}
</blockquote>
</div>
{% endif %}
{# Here flex-table stay open ! read above #} {# Here flex-table stay open ! read above #}
{% endif %} {% endif %}

View File

@ -1,7 +1,17 @@
{% macro metadata(w) %} {% macro metadata(w, include_notif_counter = true) %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %} {% if include_notif_counter == true %}
{% if notif_counter.total > 0 %} {% set more = [] %}
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }} {% for e in w.accompanyingPeriodWorkEvaluations %}
{% for d in e.documents %}
{% set more = more|merge([{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id}]) %}
{% endfor %}
{% endfor %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, more) %}
{% if notif_counter.total > 0 %}
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, more) }}
{% endif %}
{% endif %} {% endif %}
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %} {% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
{{ macro.updatedBy(w) }} {{ macro.updatedBy(w) }}
@ -20,4 +30,4 @@
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, [], suppEvaluations) }} {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, [], suppEvaluations) }}
{% endmacro %} {% endmacro %}

View File

@ -3,6 +3,7 @@
# - displayContent: [short|long] default: short # - displayContent: [short|long] default: short
#} #}
{% if w.results|length > 0 %} {% if w.results|length > 0 %}
<table class="obj-res-eval"> <table class="obj-res-eval">
<thead> <thead>
<th class="obj"><h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4></th> <th class="obj"><h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4></th>
@ -64,9 +65,11 @@
</th> </th>
</thead> </thead>
<tbody> <tbody>
{% for e in w.accompanyingPeriodWorkEvaluations %} {% if onlyone|default(false) %}
<tr> {% for e in w.accompanyingPeriodWorkEvaluations %}
<td class="eval"> {% if evalId is defined and evalId == e.id %}
<tr>
<td class="eval">
<ul class="eval_title"> <ul class="eval_title">
<li> <li>
{{ e.evaluation.title|localize_translatable_string }} {{ e.evaluation.title|localize_translatable_string }}
@ -78,13 +81,15 @@
{% if e.endDate %} {% if e.endDate %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b> <b>{{ e.endDate|format_date('short') }}</b>
</li> </li>
{% else %} {% else %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
@ -92,13 +97,15 @@
{% if e.maxDate %} {% if e.maxDate %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b> <b>{{ e.maxDate|format_date('short') }}</b>
</li> </li>
{% else %} {% else %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
@ -107,13 +114,15 @@
{% if e.warningInterval and e.warningInterval.d > 0 %} {% if e.warningInterval and e.warningInterval.d > 0 %}
<li> <li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %} {% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }} {{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li> </li>
{% else %} {% else %}
{% if displayContent is defined and displayContent == 'long' %} {% if displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
@ -122,45 +131,166 @@
{% if e.timeSpent is not null and e.timeSpent > 0 %} {% if e.timeSpent is not null and e.timeSpent > 0 %}
<li> <li>
{% set minutes = (e.timeSpent / 60) %} {% set minutes = (e.timeSpent / 60) %}
<span class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }} <span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
</li> </li>
{% elseif displayContent is defined and displayContent == 'long' %} {% elseif displayContent is defined and displayContent == 'long' %}
<li> <li>
<span class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> <span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span> <span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</li> </li>
</ul> </ul>
{% if displayContent is defined and displayContent == 'long' %} {% endif %}
{% if e.comment is not empty %}
<blockquote class="chill-user-quote">{{ e.comment|chill_entity_render_box }}</blockquote> {% endfor %}
{% if recordAction is defined %}
{{ recordAction }}
{% endif %}
{% else %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
<tr>
<td class="eval">
<ul class="eval_title">
<li>
{{ e.evaluation.title|localize_translatable_string }}
<ul class="columns">
<li>
<span
class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ e.startDate|format_date('short') }}</b>
</li>
{% if e.endDate %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.maxDate %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.warningInterval and e.warningInterval.d > 0 %}
<li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.timeSpent is not null and e.timeSpent > 0 %}
<li>
{% set minutes = (e.timeSpent / 60) %}
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
</li>
{% elseif displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
</ul>
</li>
</ul>
{% if recordAction is defined %}
{{ recordAction }}
{% endif %} {% endif %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %} {% if displayContent is defined and displayContent == 'long' %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% if e.comment is not empty %}
<blockquote
class="chill-user-quote">{{ e.comment|chill_entity_render_box }}</blockquote>
{% endif %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% if e.documents|length > 0 %}
<table class="table table-hover align-middle mt-4 mx-auto">
{% for d in e.documents %}
<tr>
<td class="border-0">{{ d.title }}</td>
<td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td>
<td class="border-0 text-end">
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-sm btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}"
data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify btn-sm"
title="{{ 'notification.Notify'|trans }}"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id }) }}"></a>
{% endif %}
{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
{% endif %}
{% if e.documents|length > 0 %}
<table class="table mt-4 mx-auto">
{% for d in e.documents %}
<tr class="border-0">
<td class="border-0">{{ d.title }}</td>
<td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td>
<td class="border-0 text-end">{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
{% endif %} {% endif %}
</td>
</tr>
{% endif %} {% endfor %}
</td> {% endif %}
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}

View File

@ -26,7 +26,8 @@
'displayAction': true, 'displayAction': true,
'displayContent': 'short', 'displayContent': 'short',
'displayFontSmall': true, 'displayFontSmall': true,
'itemBlocClass': '' 'itemBlocClass': '',
'displayNotification': true
} %} } %}
{% endfor %} {% endfor %}
</div> </div>

View File

@ -27,7 +27,20 @@
'displayContent': 'long', 'displayContent': 'long',
'itemBlocClass': 'uniq extended', 'itemBlocClass': 'uniq extended',
} %} } %}
<div class="p-3 mt-3">{{ macro.metadata(work) }}</div> <div class="p-3 mt-3">{{ macro.metadata(work, false) }}</div>
</div>
<div class="notification notification-list">
{% set more = [] %}
{% for e in work.accompanyingPeriodWorkEvaluations %}
{% for d in e.documents %}
{% set more = more|merge([{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id}]) %}
{% endfor %}
{% endfor %}
{% set notifications = chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', work.id, more) %}
{% if notifications is not empty %}
{{ notifications|raw }}
{% endif %}
</div> </div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
@ -36,6 +49,25 @@
class="btn btn-cancel">{{ 'Back to the list'|trans }}</a> class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
</li> </li>
<li>{{ macro.workflowButton(work) }}</li> <li>{{ macro.workflowButton(work) }}</li>
<li>
<div class="d-grid gap-2 {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}btn-group{% endif %}" {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}role="group"{% endif %}>
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify" title="{{ 'notification.Notify'|trans }}" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id}) }}">
</a>
{% endif %}
</div>
</li>
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', work) %} {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', work) %}
<li> <li>
<a class="btn btn-edit" <a class="btn btn-edit"
@ -47,7 +79,8 @@
<li> <li>
<a class="btn btn-delete" <a class="btn btn-delete"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': work.id } ) }}" href="{{ path('chill_person_accompanying_period_work_delete', { 'id': work.id } ) }}"
>{{ 'Delete'|trans }}</a> title = "{{ 'Delete'|trans }}"
></a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -0,0 +1,134 @@
{% if document is not null %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
<div class="flex-table accompanying-course-work">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_DOCUMENT_SHOW', document) %}
{% set doc = document %}
<div class="item-bloc evaluation-item bg-chill-llight-gray">
<div class="item-row mb-2">
<h1>{{ "Document"|trans }}: {{ doc.title }}</h1>
</div>
<div class="item-row mb-2">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ evaluation.accompanyingPeriodWork.socialAction|chill_entity_render_string }}
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.startDate|format_date('short') }}</b>
</li>
{% if evaluation.accompanyingPeriodWork.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.endDate|format_date('short') }}</b>
</li>
{% endif %}
</ul>
</span>
</h2>
</div>
<div class="item-row mb-2">
<div class="item-col" style="width: 17%;">
<h4 class="title_label">
{{ 'Participants'|trans }}
</h4>
</div>
<div class="item-col list">
{% for p in evaluation.accompanyingPeriodWork.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3">
<thead>
<tr>
<th class="eval">
<h4 class="title_label">
{{ 'Évaluation'|trans }}
</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="eval">
<ul class="eval_title">
<li class="my-2">
{{ evaluation.evaluation.title|localize_translatable_string }}
<ul class="columns pt-2">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.startDate|format_date('short') }}</b>
</li>
{% if evaluation.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.endDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.maxDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.warningInterval and evaluation.warningInterval.d > 0 %}
<li>
{% set days = (evaluation.warningInterval.d + evaluation.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% endif %}
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>
<b>{{ evaluation.createdAt|format_date('short') }}</b>
{% endif %}
</li>
</ul>
{% if evaluation.comment %}
<blockquote class="chill-user-quote" style="margin-left: 0;">
{{ evaluation.comment }}
</blockquote>
{% endif %}
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<div class="item-row">
<ul class="record_actions">
<li>
<a class="btn btn-show"
href="{{ path('chill_person_accompanying_period_work_show', { 'id': document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork.id }) }}"
title="{{ 'See the document'|trans }}"></a>
</li>
</ul>
</div>
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'This is the minimal period details'|trans ~ ': ' ~ document.id }}<br>
{{ 'You are getting a notification for a period you are not allowed to see'|trans }}
</div>
{% endif %}
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a period which does not exists any more'|trans }}
</div>
{% endif %}

View File

@ -0,0 +1,30 @@
{% macro recordAction(work) %}
<li>
<a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': work.id }) }}"
></a>
</li>
{% endmacro %}
{% if work is not null %}
<div class="flex-table accompanying-course-work">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE', work) %}
{% include "@ChillPerson/AccompanyingCourseWork/_item.html.twig" with {
'itemBlocClass': 'bg-chill-llight-gray',
'displayAction': true,
'displayContent': 'short',
'displayFontSmall': true,
'displayNotification:':true,
'w': work
} %}
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'This is the minimal period details'|trans ~ ': ' ~ work.id }}<br>
{{ 'You are getting a notification for a period you are not allowed to see'|trans }}
</div>
{% endif %}
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a period which does not exists any more'|trans }}
</div>
{% endif %}

View File

@ -8,7 +8,7 @@
{% if period is not null %} {% if period is not null %}
<div class="flex-table"> <div class="flex-table">
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', period) %} {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', period) %}
{% include 'ChillPersonBundle:AccompanyingPeriod:_list_item.html.twig' with { {% include "@ChillPerson/AccompanyingPeriod/_list_item.html.twig" with {
'recordAction': _self.recordAction(notification.relatedEntityId), 'recordAction': _self.recordAction(notification.relatedEntityId),
'itemBlocClass': 'bg-chill-llight-gray' 'itemBlocClass': 'bg-chill-llight-gray'
} %} } %}

View File

@ -2,3 +2,9 @@ services:
Chill\PersonBundle\Notification\AccompanyingPeriodNotificationHandler: Chill\PersonBundle\Notification\AccompanyingPeriodNotificationHandler:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\PersonBundle\Notification\AccompanyingPeriodWorkNotificationHandler:
autowire: true
autoconfigure: true
Chill\PersonBundle\Notification\AccompanyingPeriodWorkEvaluationDocumentNotificationHandler:
autowire: true
autoconfigure: true