From bb65909bfa66543eff7289d17997f504fcc48534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 16:52:11 +0200 Subject: [PATCH] add docgen context for a list of activities in a course --- .../Repository/ActivityACLAwareRepository.php | 89 ++++++ .../ActivityACLAwareRepositoryInterface.php | 13 + ...tActivitiesByAccompanyingPeriodContext.php | 276 ++++++++++++++++++ .../translations/messages.fr.yml | 2 + 4 files changed, 380 insertions(+) create mode 100644 src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 48f750c26..31b293a40 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -12,13 +12,21 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Entity\ActivityPresence; +use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; +use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; +use Chill\MainBundle\Entity\Location; +use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Query\ResultSetMappingBuilder; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Security; @@ -72,6 +80,87 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); } + public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array + { + $rsm = new ResultSetMappingBuilder($this->em); + + $sql = " + SELECT + a.id AS activity_id, + date, + CASE WHEN durationtime IS NOT NULL THEN (EXTRACT(EPOCH from durationtime) / 60)::int ELSE 0 END AS durationtimeminute, + attendee_id, + comment_comment, + emergency, + sentreceived, + CASE WHEN traveltime IS NOT NULL THEN (EXTRACT(EPOCH from traveltime) / 60)::int ELSE 0 END AS traveltimeminute, + t.id AS type_id, t.name as type_name, + p.id AS presence_id, p.name AS presence_name, + location.id AS location_id, location.address_id, location.name AS location_name, location.phonenumber1, location.phonenumber2, location.email, + location.locationtype_id, locationtype.title AS locationtype_title, + users.userids AS userids, + thirdparties.thirdpartyids, + persons.personids, + actions.socialactionids, + issues.socialissueids + + FROM activity a + LEFT JOIN chill_main_location location ON a.location_id = location.id + LEFT JOIN chill_main_location_type locationtype ON location.locationtype_id = locationtype.id + LEFT JOIN activitytpresence p ON a.attendee_id = p.id + LEFT JOIN activitytype t ON a.type_id = t.id + LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE + + WHERE accompanyingperiod_id = ? + ORDER BY a.date DESC, a.id DESC + LIMIT ? + "; + + $rsm + ->addEntityResult(Activity::class, 'a') + ->addFieldResult('a', 'activity_id', 'id') + ->addFieldResult('a', 'date', 'date') + ->addFieldResult('a', 'comment', 'comment') + ->addFieldResult('a', 'sentreceived', 'sentReceived') + ->addFieldResult('a', 'emergency', 'emergency') + ->addJoinedEntityResult(Location::class, 'location', 'a', 'location') + ->addFieldResult('location', 'location_id', 'id') + ->addFieldResult('location', 'location_name', 'name') + ->addFieldResult('location', 'phonenumber1', 'phonenumber1') + ->addFieldResult('location', 'phonenumber2', 'phonenumber2') + ->addFieldResult('location', 'email', 'email') + ->addJoinedEntityResult(LocationType::class,'locationType', 'location', 'locationType' ) + ->addFieldResult('locationType', 'locationtype_id', 'id') + ->addFieldResult('locationType', 'locationtype_title', 'title') + ->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType') + ->addFieldResult('activityType', 'type_id', 'id') + ->addFieldResult('activityType', 'type_name', 'name') + ->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee') + ->addFieldResult('activityPresence', 'presence_id', 'id') + ->addFieldResult('activityPresence', 'presence_name', 'name') + + // results which cannot be mapped into entity + ->addScalarResult('comment_comment', 'comment', Types::TEXT) + ->addScalarResult('userids', 'userIds', Types::JSON) + ->addScalarResult('thirdpartyids', 'thirdPartyIds', Types::JSON) + ->addScalarResult('personids', 'personIds', Types::JSON) + ->addScalarResult('socialactionids', 'socialActionIds', Types::JSON) + ->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON) + ->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER) + ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER) + ; + + $nq = $this->em->createNativeQuery($sql, $rsm); + + $nq->setParameter(0, $period->getId())->setParameter(1, $limit); + + return $nq->getResult(AbstractQuery::HYDRATE_ARRAY); + } + /** * @param array $orderBy * diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index 56fb112f9..a6f8fe934 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -21,6 +21,19 @@ interface ActivityACLAwareRepositoryInterface */ public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; + /** + * Return a list of activities, simplified as array (not object). + * + * The aim of this method is to get a long list of activities and keep performance. + * + * @param AccompanyingPeriod $period + * @param string $role + * @param int|null $limit + * @param array|null $orderBy + * @return array an array of array, each item representing an activity + */ + public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; + /** * @return Activity[]|array */ diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php new file mode 100644 index 000000000..3868545d9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -0,0 +1,276 @@ +accompanyingPeriodContext = $accompanyingPeriodContext; + $this->activityACLAwareRepository = $activityACLAwareRepository; + $this->normalizer = $normalizer; + $this->personRepository = $personRepository; + $this->socialActionRepository = $socialActionRepository; + $this->socialIssueRepository = $socialIssueRepository; + $this->thirdPartyRepository = $thirdPartyRepository; + $this->translatableStringHelper = $translatableStringHelper; + $this->userRepository = $userRepository; + } + + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + { + $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData); + + $data['activities'] = $this->getActivitiesSimplified($entity); + + return $data; + } + + private function getActivitiesSimplified(AccompanyingPeriod $period) + { + $activities = + $this->activityACLAwareRepository->findByAccompanyingPeriodSimplified($period); + $results = []; + + foreach ($activities as $row) { + $activity = $row[0]; + + $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class + ]); + + if (null === $activity['location']) { + $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class + ]); + $activity['location']['type'] = 'location'; + } else { + $activity['location']['isNull'] = false; + $activity['location']['type'] = 'location'; + foreach (['1', '2'] as $key) { + $activity['location']['phonenumber'.$key] = $this->normalizer->normalize( + $activity['location']['phonenumber'.$key], + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class] + ); + } + } + + if (is_numeric($activity['location']['locationType']['id'])) { + $activity['location']['locationType']['title'] = $this->translatableStringHelper->localize( + $activity['location']['locationType']['title'] + ); + $activity['location']['locationType']['isNull'] = false; + $activity['location']['locationType']['type'] = 'locationType'; + } + + if (null !== $activity['activityType']) { + $activity['activityType']['name'] = $this->translatableStringHelper->localize( + $activity['activityType']['name'] + ); + $activity['activityType']['isNull'] = false; + $activity['activityType']['type'] = 'activityType'; + } else { + $activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class + ]); + } + + if (null !== $activity['attendee']) { + $activity['attendee']['name'] = $this->translatableStringHelper->localize( + $activity['attendee']['name'] + ); + $activity['attendee']['isNull'] = false; + $activity['attendee']['type'] = 'activityPresence'; + } else { + $activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class + ]); + } + + $activity['comment'] = (string) $row['comment']; + $activity['travelTimeMinute'] = $row['travelTimeMinute']; + $activity['durationTimeMinute'] = $row['durationTimeMinute']; + + if (null !== $row['userIds']) { + foreach ($row['userIds'] as $id) { + $activity['users'][] = $this->normalizer->normalize( + $this->userRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class] + ); + } + } else { + $activity['users'] = []; + } + + if (null !== $row['personIds']) { + foreach ($row['personIds'] as $id) { + $activity['persons'][] = $this->normalizer->normalize( + $this->personRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Person::class] + ); + } + } else { + $activity['persons'] = []; + } + + if (null !== $row['thirdPartyIds']) { + foreach ($row['thirdPartyIds'] as $id) { + $activity['thirdParties'][] = $this->normalizer->normalize( + $this->thirdPartyRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ThirdParty::class] + ); + } + } else { + $activity['thirdParties'] = []; + } + + if (null !== $row['socialActionIds']) { + foreach ($row['socialActionIds'] as $id) { + $activity['socialActions'][] = $this->normalizer->normalize( + $this->socialActionRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialAction::class] + ); + } + } else { + $activity['socialActions'] = []; + } + + if (null !== $row['socialIssueIds']) { + foreach ($row['socialIssueIds'] as $id) { + $activity['socialIssues'][] = $this->normalizer->normalize( + $this->socialIssueRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialIssue::class] + ); + } + } else { + $activity['socialIssues'] = []; + } + + $results[] = $activity; + } + + return $results; + } + + public function getDescription(): string + { + return 'docgen.Accompanying period with a list of activities description'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriod::class; + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'docgen.Accompanying period with a list of activities'; + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); + } + + public function adminFormReverseTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormReverseTransform($data); + } + + public function adminFormTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormTransform($data); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->accompanyingPeriodContext->buildAdminForm($builder); + } + + public function hasAdminForm(): bool + { + return $this->accompanyingPeriodContext->hasAdminForm(); + } + + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->accompanyingPeriodContext->getFormData($template, $entity); + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); + } + +} diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 051497b69..49217dd5d 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -232,3 +232,5 @@ This is the minimal activity data: Activité n° docgen: Activity basic: Echange A basic context for activity: Contexte pour les échanges + Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges + Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés.