From 1281002addc62bd023a38a04e13a32217252856a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9becca=20Tinchon?= Date: Mon, 14 Oct 2024 10:02:22 +0200 Subject: [PATCH] presence management --- .../Controller/EventPresenceController.php | 36 ++++++++------ src/plugin/cursus/Entity/EventPresence.php | 33 +++++++++++++ .../Migrations/Version20241014084016.php | 47 +++++++++++++++++++ .../cursus/Manager/EventPresenceManager.php | 2 +- .../actions/presence/confirm-presence.js | 25 ---------- .../modules/event/components/list.jsx | 13 +++++ .../modules/event/components/page.jsx | 15 +++++- src/plugin/cursus/Resources/modules/plugin.js | 1 - .../modules/presence/components/list.jsx | 13 +++++ .../Resources/translations/presence.en.json | 6 ++- .../Resources/translations/presence.fr.json | 6 ++- .../Serializer/EventPresenceSerializer.php | 11 +++++ 12 files changed, 162 insertions(+), 46 deletions(-) create mode 100644 src/plugin/cursus/Installation/Migrations/Version20241014084016.php delete mode 100644 src/plugin/cursus/Resources/modules/actions/presence/confirm-presence.js diff --git a/src/plugin/cursus/Controller/EventPresenceController.php b/src/plugin/cursus/Controller/EventPresenceController.php index be9408172bd..8aac1dfc2e1 100644 --- a/src/plugin/cursus/Controller/EventPresenceController.php +++ b/src/plugin/cursus/Controller/EventPresenceController.php @@ -91,34 +91,39 @@ public function signStatusAction(Request $request): JsonResponse return new JsonResponse(['success' => false]); } - $presenceData = $this->serializer->serialize($presence); - $presenceData['status'] = EventPresence::PRESENT; - $presenceData['signature'] = $signature; + $presence->setStatus(EventPresence::PRESENT); + $presence->setSignature($signature); + $presence->setPresenceUpdatedBy($this->tokenStorage->getToken()->getUser()); + $presence->setPresenceUpdatedAt(new \DateTime()); - $this->crud->update($presence, $presenceData); + $this->om->persist($presence); + $this->om->flush(); return new JsonResponse(['success' => true]); } /** - * Confirm the status of a EventPresence for current user. + * Confirm the status of a EventPresence for current event. + * + * @Route("/confirm/{id}", name="apiv2_cursus_event_presence_confirm", methods={"PUT"}) * - * @Route("/confirm", name="apiv2_cursus_event_presence_confirm", methods={"PUT"}) + * @EXT\ParamConverter("event", class="Claroline\CursusBundle\Entity\Event", options={"mapping": {"id": "uuid"}}) */ - public function confirmStatusAction(Request $request): JsonResponse + public function confirmStatusAction(Event $event): JsonResponse { - $data = $this->decodeRequest($request); - if (empty($data)) { - throw new InvalidDataException('Invalid data'); - } + $this->checkPermission('ADMINISTRATE', $event, [], true); + + $presences = $this->om->getRepository(EventPresence::class)->findBy(['event' => $event]); + $presencesToValidate = array_filter($presences, function (EventPresence $presence) { + return EventPresence::UNKNOWN !== $presence->getStatus(); + }); - $presences = $this->om->getRepository(EventPresence::class)->findBy(['uuid' => $data]); $this->om->startFlushSuite(); - foreach ($presences as $presence) { - $this->checkPermission('ADMINISTRATE', $presence, [], true); + foreach ($presencesToValidate as $presence) { $this->manager->setValidationDate([$presence], new \DateTime()); } + $this->om->endFlushSuite(); return new JsonResponse(); @@ -211,6 +216,9 @@ public function updateStatusAction(string $status, Request $request): JsonRespon $this->checkPermission('EDIT', $presence, [], true); $this->manager->setStatus([$presence], $status); + + $presence->setPresenceUpdatedBy($this->tokenStorage->getToken()->getUser()); + $presence->setPresenceUpdatedAt(new \DateTime()); } $this->om->endFlushSuite(); diff --git a/src/plugin/cursus/Entity/EventPresence.php b/src/plugin/cursus/Entity/EventPresence.php index dd25bb865d0..f176d9faf57 100644 --- a/src/plugin/cursus/Entity/EventPresence.php +++ b/src/plugin/cursus/Entity/EventPresence.php @@ -62,6 +62,17 @@ class EventPresence */ private ?array $evidences = null; + /** + * @ORM\ManyToOne(targetEntity="Claroline\CoreBundle\Entity\User") + * @ORM\JoinColumn(name="presence_updated_by", referencedColumnName="id", nullable=true) + */ + private ?User $presenceUpdatedBy = null; + + /** + * @ORM\Column(name="presence_updated_at", type="datetime", nullable=true) + */ + private ?\DateTimeInterface $presenceUpdatedAt = null; + public function __construct() { $this->refreshUuid(); @@ -126,4 +137,26 @@ public function setEvidences(?array $evidences): void { $this->evidences = $evidences; } + + public function getPresenceUpdatedBy(): ?User + { + return $this->presenceUpdatedBy; + } + + public function setPresenceUpdatedBy(?User $user): self + { + $this->presenceUpdatedBy = $user; + return $this; + } + + public function getPresenceUpdatedAt(): ?\DateTimeInterface + { + return $this->presenceUpdatedAt; + } + + public function setPresenceUpdatedAt(?\DateTimeInterface $date): self + { + $this->presenceUpdatedAt = $date; + return $this; + } } diff --git a/src/plugin/cursus/Installation/Migrations/Version20241014084016.php b/src/plugin/cursus/Installation/Migrations/Version20241014084016.php new file mode 100644 index 00000000000..0a33576739f --- /dev/null +++ b/src/plugin/cursus/Installation/Migrations/Version20241014084016.php @@ -0,0 +1,47 @@ +addSql(' + ALTER TABLE claro_cursusbundle_presence_status + ADD presence_updated_by INT DEFAULT NULL, + ADD presence_updated_at DATETIME DEFAULT NULL + '); + $this->addSql(' + ALTER TABLE claro_cursusbundle_presence_status + ADD CONSTRAINT FK_DFE5E1FE349A94C7 FOREIGN KEY (presence_updated_by) + REFERENCES claro_user (id) + '); + $this->addSql(' + CREATE INDEX IDX_DFE5E1FE349A94C7 ON claro_cursusbundle_presence_status (presence_updated_by) + '); + } + + public function down(Schema $schema): void + { + $this->addSql(' + ALTER TABLE claro_cursusbundle_presence_status + DROP FOREIGN KEY FK_DFE5E1FE349A94C7 + '); + $this->addSql(' + DROP INDEX IDX_DFE5E1FE349A94C7 ON claro_cursusbundle_presence_status + '); + $this->addSql(' + ALTER TABLE claro_cursusbundle_presence_status + DROP presence_updated_by, + DROP presence_updated_at + '); + } +} diff --git a/src/plugin/cursus/Manager/EventPresenceManager.php b/src/plugin/cursus/Manager/EventPresenceManager.php index 5727a810e42..cec148e0e58 100644 --- a/src/plugin/cursus/Manager/EventPresenceManager.php +++ b/src/plugin/cursus/Manager/EventPresenceManager.php @@ -92,7 +92,7 @@ public function setStatus(array $presences, string $status): array return $presences; } - public function setValidationDate(array $presences, \DateTimeInterface $date): array + public function setValidationDate(array $presences, ?\DateTimeInterface $date): array { foreach ($presences as $presence) { $presence->setValidationDate($date); diff --git a/src/plugin/cursus/Resources/modules/actions/presence/confirm-presence.js b/src/plugin/cursus/Resources/modules/actions/presence/confirm-presence.js deleted file mode 100644 index 9375029e76e..00000000000 --- a/src/plugin/cursus/Resources/modules/actions/presence/confirm-presence.js +++ /dev/null @@ -1,25 +0,0 @@ -import {ASYNC_BUTTON} from '#/main/app/buttons' -import {trans} from '#/main/app/intl' -import {hasPermission} from '#/main/app/security' - -export default (presences, refresher) => { - const processable = presences.filter(presence => hasPermission('administrate', presence)) - - return { - name: 'confirm-presence', - type: ASYNC_BUTTON, - icon: 'fa fa-fw fa-clipboard-check', - label: trans('presence_validation', {}, 'presence'), - request: { - url: ['apiv2_cursus_event_presence_confirm'], - request: { - method: 'PUT', - body: JSON.stringify(processable.map(presence => presence.id)) - }, - success: refresher.update - }, - displayed: 0 !== processable.length, - group: trans('validation', {}, 'presence'), - scope: ['collection', 'object'] - } -} diff --git a/src/plugin/cursus/Resources/modules/event/components/list.jsx b/src/plugin/cursus/Resources/modules/event/components/list.jsx index 1317a120df9..f387722ae0c 100644 --- a/src/plugin/cursus/Resources/modules/event/components/list.jsx +++ b/src/plugin/cursus/Resources/modules/event/components/list.jsx @@ -103,6 +103,19 @@ const Events = (props) => displayed: hasPermission('edit', rows[0]), group: trans('presences', {}, 'cursus'), target: ['apiv2_cursus_event_presence_download', {id: rows[0].id, filled: 1}] + }, { + name: 'confirm-status', + type: ASYNC_BUTTON, + icon: 'fa fa-fw fa-clipboard-check', + label: trans('presence_validation', {}, 'presence'), + displayed: hasPermission('edit', rows[0]), + group: trans('validation', {}, 'presence'), + request: { + url: ['apiv2_cursus_event_presence_confirm', {id: rows[0].id}], + request: { + method: 'PUT' + } + } } ].concat(props.customActions(rows))} delete={{ diff --git a/src/plugin/cursus/Resources/modules/event/components/page.jsx b/src/plugin/cursus/Resources/modules/event/components/page.jsx index c5905174013..33f28a07eaa 100644 --- a/src/plugin/cursus/Resources/modules/event/components/page.jsx +++ b/src/plugin/cursus/Resources/modules/event/components/page.jsx @@ -5,7 +5,7 @@ import isEmpty from 'lodash/isEmpty' import {trans} from '#/main/app/intl/translation' import {hasPermission} from '#/main/app/security' -import {MODAL_BUTTON, URL_BUTTON} from '#/main/app/buttons' +import {MODAL_BUTTON, URL_BUTTON, ASYNC_BUTTON} from '#/main/app/buttons' import {ContentLoader} from '#/main/app/content/components/loader' import {ToolPage} from '#/main/core/tool' @@ -82,6 +82,19 @@ const EventPage = (props) => { displayed: hasPermission('edit', props.event), group: trans('presences', {}, 'cursus'), target: ['apiv2_cursus_event_presence_download', {id: props.event.id, filled: 1}] + }, { + name: 'confirm-status', + type: ASYNC_BUTTON, + icon: 'fa fa-fw fa-clipboard-check', + label: trans('presence_validation', {}, 'presence'), + displayed: hasPermission('edit', props.event), + group: trans('validation', {}, 'presence'), + request: { + url: ['apiv2_cursus_event_presence_confirm', {id: props.event.id}], + request: { + method: 'PUT' + } + } } ]} diff --git a/src/plugin/cursus/Resources/modules/plugin.js b/src/plugin/cursus/Resources/modules/plugin.js index 48b74fe4c98..a8012452ea1 100644 --- a/src/plugin/cursus/Resources/modules/plugin.js +++ b/src/plugin/cursus/Resources/modules/plugin.js @@ -53,7 +53,6 @@ registry.add('ClarolineCursusBundle', { 'mark-absent-unjustified': () => { return import(/* webpackChunkName: "training-action-presence-absent-unjustified" */ '#/plugin/cursus/actions/presence/mark-absent-unjustified') }, 'mark-absent-present' : () => { return import(/* webpackChunkName: "training-action-presence-present" */ '#/plugin/cursus/actions/presence/mark-present') }, 'mark-unknown' : () => { return import(/* webpackChunkName: "training-action-presence-unknown" */ '#/plugin/cursus/actions/presence/mark-unknown') }, - 'confirm-presence' : () => { return import(/* webpackChunkName: "training-action-presence-confirm" */ '#/plugin/cursus/actions/presence/confirm-presence') }, 'add-evidence' : () => { return import(/* webpackChunkName: "training-action-presence-add-evidence" */ '#/plugin/cursus/actions/presence/add-evidence') } } }, diff --git a/src/plugin/cursus/Resources/modules/presence/components/list.jsx b/src/plugin/cursus/Resources/modules/presence/components/list.jsx index 9e4817ca54b..a7c172c92a4 100644 --- a/src/plugin/cursus/Resources/modules/presence/components/list.jsx +++ b/src/plugin/cursus/Resources/modules/presence/components/list.jsx @@ -46,6 +46,19 @@ const Presences = props => { ), displayed: true }, { + name: 'presence_updated_by', + type: 'user', + label: trans('presence_updated_by', {}, 'presence'), + displayed: true + }, { + name: 'presence_updated_at', + type: 'date', + label: trans('presence_updated_at', {}, 'presence'), + displayed: true, + options: { + time: true + } + },{ name: 'validation_date', type: 'date', label: trans('presence_confirmation_date', {}, 'presence'), diff --git a/src/plugin/cursus/Resources/translations/presence.en.json b/src/plugin/cursus/Resources/translations/presence.en.json index a5a65b4fd00..7d20e641fd9 100644 --- a/src/plugin/cursus/Resources/translations/presence.en.json +++ b/src/plugin/cursus/Resources/translations/presence.en.json @@ -16,10 +16,12 @@ "presence_confirm_title": "Presence confirmation", "presence_confirm_desc": "Your %event_title% event signature has been successfully recorded.", "presence_confirm_other": "Confirm another presence", - "presence_confirmation_date": "Confirmation date", + "presence_confirmation_date": "Validation date", "presence_validation_date": "Validation date by tutor", "presence_info": "Me, %user%, was present at the event %event_title% from %event_date_start% to %event_date_end%.", - "presence_validation": "Validate presence", + "presence_validation": "Validate status", + "presence_updated_by": "Status updated by", + "presence_updated_at": "Update date", "validate": "Validate", "validation": "Validation" diff --git a/src/plugin/cursus/Resources/translations/presence.fr.json b/src/plugin/cursus/Resources/translations/presence.fr.json index c9589c0afd3..3aafec7970e 100644 --- a/src/plugin/cursus/Resources/translations/presence.fr.json +++ b/src/plugin/cursus/Resources/translations/presence.fr.json @@ -16,10 +16,12 @@ "presence_confirm_title": "Confirmation de présence", "presence_confirm_desc": "Votre présence à la séance %event_title% a bien été enregistrée.", "presence_confirm_other": "Confirmer une autre présence", - "presence_confirmation_date": "Date de confirmation", + "presence_confirmation_date": "Date de validation", "presence_validation_date": "Date de validation par le formateur", "presence_info": "Moi, %user%, étais présent à la séance %event_title% du %event_datetime_start% au %event_datetime_end%.", - "presence_validation": "Confirmer la présence", + "presence_validation": "Valider les statuts", + "presence_updated_by": "Statut mis à jour par", + "presence_updated_at": "Date de mise à jour", "validate": "Valider", "validation": "Validation" diff --git a/src/plugin/cursus/Serializer/EventPresenceSerializer.php b/src/plugin/cursus/Serializer/EventPresenceSerializer.php index 7f30fa8f2d7..f4c966444ee 100644 --- a/src/plugin/cursus/Serializer/EventPresenceSerializer.php +++ b/src/plugin/cursus/Serializer/EventPresenceSerializer.php @@ -54,6 +54,8 @@ public function serialize(EventPresence $eventPresence, array $options = []): ar 'signature' => $eventPresence->getSignature(), 'validation_date' => DateNormalizer::normalize($eventPresence->getValidationDate()), 'evidences' => $eventPresence->getEvidences(), + 'presence_updated_by' => $eventPresence->getPresenceUpdatedBy() ? $this->userSerializer->serialize($eventPresence->getPresenceUpdatedBy(), [SerializerInterface::SERIALIZE_MINIMAL]) : null, + 'presence_updated_at' => DateNormalizer::normalize($eventPresence->getPresenceUpdatedAt()), ]; if (!in_array(SerializerInterface::SERIALIZE_TRANSFER, $options)) { @@ -88,6 +90,15 @@ public function deserialize(array $data, EventPresence $eventPresence): EventPre $eventPresence->setEvidences($data['evidences'] ?? null); } + if (isset($data['presence_updated_by'])) { + $updatedBy = $this->om->getRepository(User::class)->findOneBy(['uuid' => $data['presence_updated_by']['id']]); + $eventPresence->setPresenceUpdatedBy($updatedBy); + } + + if (isset($data['presence_updated_at'])) { + $eventPresence->setPresenceUpdatedAt(DateNormalizer::denormalize($data['presence_updated_at'])); + } + return $eventPresence; } }