diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php index 8c98e1049..071ccd232 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php @@ -44,7 +44,7 @@ class ActivityTypeAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { if (!in_array('acttype', $qb->getAllAliases(), true)) { - $qb->join('activity.activityType', 'acttype'); + $qb->leftJoin('activity.activityType', 'acttype'); } $qb->addSelect(sprintf('IDENTITY(activity.activityType) AS %s', self::KEY)); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php new file mode 100644 index 000000000..77444e414 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php @@ -0,0 +1,165 @@ +helper = $helper; + $this->entityManager = $entityManager; + $this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper; + } + + public function buildForm(FormBuilderInterface $builder) + { + $this->helper->buildForm($builder); + } + + public function getAllowedFormattersTypes() + { + return $this->helper->getAllowedFormattersTypes(); + } + + public function getDescription() + { + return ListActivityHelper::MSG_KEY . 'List activities linked to an accompanying course'; + } + + public function getGroup(): string + { + return 'Exports of activities linked to an accompanying period'; + } + + public function getLabels($key, array $values, $data) + { + switch ($key) { + case 'acpId': + return static function ($value) { + if ('_header' === $value) { + return ListActivityHelper::MSG_KEY . 'accompanying course id'; + } + + return $value ?? ''; + }; + + case 'scopesNames': + return $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles'); + + default: + return $this->helper->getLabels($key, $values, $data); + } + } + + public function getQueryKeys($data) + { + return + array_merge( + $this->helper->getQueryKeys($data), + [ + 'acpId', + 'scopesNames', + ] + ); + } + + public function getResult($query, $data) + { + return $this->helper->getResult($query, $data); + } + + public function getTitle() + { + return ListActivityHelper::MSG_KEY . 'List activity linked to a course'; + } + + public function getType() + { + return $this->helper->getType(); + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static function ($el) { + return $el['center']; + }, $acl); + + $qb = $this->entityManager->createQueryBuilder(); + + $qb + ->distinct() + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'acp') + ->leftJoin('acp.participations', 'acppart') + ->leftJoin('acppart.person', 'person') + ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL') + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 + FROM ' . PersonCenterHistory::class . ' acl_count_person_history + WHERE acl_count_person_history.person = person + AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + // some grouping are necessary + ->addGroupBy('acp.id') + ->addOrderBy('activity.date') + ->addOrderBy('activity.id') + ->setParameter('authorized_centers', $centers); + + $this->helper->addSelect($qb); + + // add select for this step + $qb + ->addSelect('acp.id AS acpId') + ->addSelect('(SELECT AGGREGATE(acpScope.name) FROM ' . Scope::class . ' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames') + ->addGroupBy('scopesNames'); + + return $qb; + } + + public function requiredRole(): string + { + return ActivityStatsVoter::LISTS; + } + + public function supportsModifiers() + { + return array_merge( + $this->helper->supportsModifiers(), + [ + \Chill\PersonBundle\Export\Declarations::ACP_TYPE, + ] + ); + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php new file mode 100644 index 000000000..0e8b28ab4 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php @@ -0,0 +1,269 @@ +activityPresenceRepository = $activityPresenceRepository; + $this->activityTypeRepository = $activityTypeRepository; + $this->dateTimeHelper = $dateTimeHelper; + $this->labelPersonHelper = $labelPersonHelper; + $this->labelThirdPartyHelper = $labelThirdPartyHelper; + $this->translator = $translator; + $this->translatableStringHelper = $translatableStringHelper; + $this->translatableStringLabelHelper = $translatableStringLabelHelper; + $this->userHelper = $userHelper; + } + + public function addSelect(QueryBuilder $qb): void + { + $qb + ->addSelect('activity.id AS id') + ->addSelect('activity.date') + ->addSelect('IDENTITY(activity.activityType) AS typeName') + ->leftJoin('activity.reasons', 'reasons') + ->addSelect('AGGREGATE(reasons.name) AS listReasons') + ->leftJoin('activity.persons', 'actPerson') + ->addSelect('AGGREGATE(actPerson.id) AS personsIds') + ->addSelect('AGGREGATE(actPerson.id) AS personsNames') + ->leftJoin('activity.users', 'users_u') + ->addSelect('AGGREGATE(users_u.id) AS usersIds') + ->addSelect('AGGREGATE(users_u.id) AS usersNames') + ->leftJoin('activity.thirdParties', 'thirdparty') + ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds') + ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames') + ->addSelect('IDENTITY(activity.attendee) AS attendeeName') + ->addSelect('activity.durationTime') + ->addSelect('activity.travelTime') + ->addSelect('activity.emergency') + ->leftJoin('activity.location', 'location') + ->addSelect('location.name AS locationName') + ->addSelect('activity.sentReceived') + ->addSelect('IDENTITY(activity.createdBy) AS createdBy') + ->addSelect('activity.createdAt') + ->addSelect('IDENTITY(activity.updatedBy) AS updatedBy') + ->addSelect('activity.updatedAt') + ->addGroupBy('activity.id') + ->addGroupBy('location.id'); + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_LIST]; + } + + public function getLabels($key, array $values, $data) + { + switch ($key) { + case 'createdAt': + case 'updatedAt': + return $this->dateTimeHelper->getLabel($key); + + case 'createdBy': + case 'updatedBy': + return $this->userHelper->getLabel($key, $values, $key); + + case 'date': + return $this->dateTimeHelper->getLabel(self::MSG_KEY . $key); + + case 'attendeeName': + return function ($value) { + if ('_header' === $value) { + return 'Attendee'; + } + + if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($presence->getName()); + }; + + case 'listReasons': + return $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'); + + case 'typeName': + return function ($value) { + if ('_header' === $value) { + return 'Activity type'; + } + + if (null === $value || null === $type = $this->activityTypeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($type->getName()); + }; + + case 'usersNames': + return $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name'); + + case 'usersIds': + case 'thirdPartiesIds': + case 'personsIds': + return static function ($value) use ($key) { + if ('_header' === $value) { + switch ($key) { + case 'usersIds': + return self::MSG_KEY . 'users ids'; + + case 'thirdPartiesIds': + return self::MSG_KEY . 'third parties ids'; + + case 'personsIds': + return self::MSG_KEY . 'persons ids'; + + default: + throw new LogicException('key not supported'); + } + } + + $decoded = json_decode($value); + + return implode( + '|', + array_unique( + array_filter($decoded, static fn (?int $id) => null !== $id), + SORT_NUMERIC + ) + ); + }; + + case 'personsNames': + return $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name'); + + case 'thirdPartiesNames': + return $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties'); + + case 'sentReceived': + return function ($value) { + if ('_header' === $value) { + return self::MSG_KEY . 'sent received'; + } + + if (null === $value) { + return ''; + } + + return $this->translator->trans($value); + }; + + default: + return function ($value) use ($key) { + if ('_header' === $value) { + return self::MSG_KEY . $key; + } + + if (null === $value) { + return ''; + } + + return $this->translator->trans($value); + }; + } + } + + public function getQueryKeys($data) + { + return [ + 'id', + 'date', + 'typeName', + 'listReasons', + 'attendeeName', + 'durationTime', + 'travelTime', + 'emergency', + 'locationName', + 'sentReceived', + 'personsIds', + 'personsNames', + 'usersIds', + 'usersNames', + 'thirdPartiesIds', + 'thirdPartiesNames', + 'createdBy', + 'createdAt', + 'updatedBy', + 'updatedAt', + ]; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); + } + + public function getType(): string + { + return Declarations::ACTIVITY; + } + + public function supportsModifiers() + { + return [ + Declarations::ACTIVITY, + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php new file mode 100644 index 000000000..2cf9f9470 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepository.php @@ -0,0 +1,51 @@ +repository = $entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?ActivityPresence + { + return $this->repository->find($id); + } + + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array + { + return $this->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?ActivityPresence + { + return $this->findOneBy($criteria); + } + + public function getClassName(): string + { + return ActivityPresence::class; + } +} diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php new file mode 100644 index 000000000..228d70856 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php @@ -0,0 +1,33 @@ +setName('chill:main:address-ref-from-best-addresses') - ->addArgument('lang', InputArgument::REQUIRED) - ->addArgument('list', InputArgument::IS_ARRAY, 'The list to add'); + ->addArgument('lang', InputArgument::REQUIRED, "Language code, for example 'fr'") + ->addArgument('list', InputArgument::IS_ARRAY, "The list to add, for example 'full', or 'extract' (dev) or '1xxx' (brussel CP)") + ->setDescription('Import BE addresses from BeST Address (see https://osoc19.github.io/best/)'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php b/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php index 6d2737a66..40772d52b 100644 --- a/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php +++ b/src/Bundle/ChillMainBundle/Command/LoadAddressesFRFromBANOCommand.php @@ -31,7 +31,7 @@ class LoadAddressesFRFromBANOCommand extends Command { $this->setName('chill:main:address-ref-from-bano') ->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers') - ->setDescription('Import addresses from bano (see https://bano.openstreetmap.fr'); + ->setDescription('Import FR addresses from bano (see https://bano.openstreetmap.fr'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php index 8b3175408..d86770560 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php @@ -133,8 +133,7 @@ class EntityWorkflowStep if (!$this->destUser->contains($user)) { $this->destUser[] = $user; $this->getEntityWorkflow() - ->addSubscriberToFinal($user) - ->addSubscriberToStep($user); + ->addSubscriberToFinal($user); } return $this; @@ -145,8 +144,7 @@ class EntityWorkflowStep if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) { $this->destUserByAccessKey[] = $user; $this->getEntityWorkflow() - ->addSubscriberToFinal($user) - ->addSubscriberToStep($user); + ->addSubscriberToFinal($user); } return $this; diff --git a/src/Bundle/ChillMainBundle/Export/Helper/TranslatableStringExportLabelHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/TranslatableStringExportLabelHelper.php new file mode 100644 index 000000000..44ce2b194 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/TranslatableStringExportLabelHelper.php @@ -0,0 +1,70 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function getLabel(string $key, array $values, string $header) + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + return $this->translatableStringHelper->localize(json_decode($value, true)); + }; + } + + public function getLabelMulti(string $key, array $values, string $header) + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + $decoded = json_decode($value, true); + + return implode( + '|', + array_unique( + array_map( + fn (array $translatableString) => $this->translatableStringHelper->localize($translatableString), + array_filter($decoded, static fn ($elem) => null !== $elem) + ) + ) + ); + }; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php index 98c4c5579..d8eb7e9cc 100644 --- a/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php +++ b/src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php @@ -13,6 +13,8 @@ namespace Chill\MainBundle\Export\Helper; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; +use function count; +use const SORT_NUMERIC; class UserHelper { @@ -40,4 +42,43 @@ class UserHelper return $this->userRender->renderString($user, []); }; } + + public function getLabelMulti($key, array $values, string $header): callable + { + return function ($value) { + if ('_header' === $value) { + return 'users name'; + } + + if (null === $value) { + return ''; + } + + $decoded = json_decode($value); + + if (0 === count($decoded)) { + return ''; + } + + return + implode( + '|', + array_map( + function (int $userId) { + $user = $this->userRepository->find($userId); + + if (null === $user) { + return ''; + } + + return $this->userRender->renderString($user, []); + }, + array_unique( + array_filter($decoded, static fn (?int $userId) => null !== $userId), + SORT_NUMERIC + ) + ) + ); + }; + } } diff --git a/src/Bundle/ChillMainBundle/Service/Import/PostalCodeBEFromBestAddress.php b/src/Bundle/ChillMainBundle/Service/Import/PostalCodeBEFromBestAddress.php index a78744a65..a6d9512c7 100644 --- a/src/Bundle/ChillMainBundle/Service/Import/PostalCodeBEFromBestAddress.php +++ b/src/Bundle/ChillMainBundle/Service/Import/PostalCodeBEFromBestAddress.php @@ -97,8 +97,8 @@ class PostalCodeBEFromBestAddress trim($record['postal_info_objectid']), $record['municipality_objectid'], 'bestaddress', - $record['Y'], - $record['X'], + (float) $record['Y'], + (float) $record['X'], 3812 ); } diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index fbd8da3a7..df6321ef2 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -56,6 +56,12 @@ Until %date%: Jusqu'au %date% until %date%: jusqu'au %date% Since: Depuis le Until: Jusqu'au + +updatedAt: Mise à jour le +updatedBy: Mise à jour par +createdAt: Créé le +createdBy: Créé par + #elements used in software centers: centres Centers: Centres @@ -471,8 +477,8 @@ workflow: Previous workflow transitionned help: Workflows où vous avez exécuté une action. For: Pour You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire. - An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces addresses - Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtennu l'accès grâce au lien reçu par email + An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses + Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email Access link copied: Lien d'accès copié This link grant any user to apply a transition: Le lien d'accès suivant permet d'appliquer une transition The workflow may be accssed through this link: Une transition peut être appliquée sur ce workflow grâce au lien d'accès suivant diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php new file mode 100644 index 000000000..2cfc8d3cf --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php @@ -0,0 +1,69 @@ +personRepository = $personRepository; + $this->personRender = $personRender; + } + + public function getLabelMulti(string $key, array $values, string $header): callable + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + $decoded = json_decode($value); + + if (0 === count($decoded)) { + return ''; + } + + return + implode( + '|', + array_map( + function (int $personId) { + $person = $this->personRepository->find($personId); + + if (null === $person) { + return ''; + } + + return $this->personRender->renderString($person, []); + }, + array_unique( + array_filter($decoded, static fn (?int $id) => null !== $id), + SORT_NUMERIC + ) + ) + ); + }; + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php b/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php new file mode 100644 index 000000000..dc5f8ddc6 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php @@ -0,0 +1,69 @@ +thirdPartyRender = $thirdPartyRender; + $this->thirdPartyRepository = $thirdPartyRepository; + } + + public function getLabelMulti(string $key, array $values, string $header): callable + { + return function ($value) use ($header) { + if ('_header' === $value) { + return $header; + } + + if (null === $value) { + return ''; + } + + $decoded = json_decode($value); + + if (0 === count($decoded)) { + return ''; + } + + return + implode( + '|', + array_map( + function (int $tpId) { + $tp = $this->thirdPartyRepository->find($tpId); + + if (null === $tp) { + return ''; + } + + return $this->thirdPartyRender->renderString($tp, []); + }, + array_unique( + array_filter($decoded, static fn (?int $id) => null !== $id), + SORT_NUMERIC + ) + ) + ); + }; + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/config/services.yaml b/src/Bundle/ChillThirdPartyBundle/config/services.yaml index 8f9420a67..0024556f6 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services.yaml @@ -6,3 +6,8 @@ services: tags: - { name: 'serializer.normalizer', priority: 64 } + Chill\ThirdPartyBundle\Export\: + autowire: true + autoconfigure: true + resource: '../Export/' +