mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	merge latest changes from upgrade-sf5 branch
This commit is contained in:
		
							
								
								
									
										6
									
								
								.changes/v2.22.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/v2.22.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| ## v2.22.0 - 2024-06-25 | ||||
| ### Feature | ||||
| * ([#216](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/216)) [event bundle] exports  added for the event module | ||||
|  | ||||
| ### Traduction francophone | ||||
| * Exports sont ajoutés pour la module événement. | ||||
							
								
								
									
										5
									
								
								.changes/v2.22.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/v2.22.1.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| ## v2.22.1 - 2024-07-01 | ||||
| ### Fixed | ||||
| * Remove debug word  | ||||
| ### DX | ||||
| * Add a command for reading official address DB from Luxembourg and update chill addresses  | ||||
							
								
								
									
										3
									
								
								.changes/v2.22.2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.changes/v2.22.2.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## v2.22.2 - 2024-07-03 | ||||
| ### Fixed | ||||
| * Remove scope required for event participation stats  | ||||
							
								
								
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -6,6 +6,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), | ||||
| and is generated by [Changie](https://github.com/miniscruff/changie). | ||||
|  | ||||
|  | ||||
| ## v2.22.2 - 2024-07-03 | ||||
| ### Fixed | ||||
| * Remove scope required for event participation stats  | ||||
|  | ||||
| ## v2.22.1 - 2024-07-01 | ||||
| ### Fixed | ||||
| * Remove debug word  | ||||
| ### DX | ||||
| * Add a command for reading official address DB from Luxembourg and update chill addresses  | ||||
|  | ||||
| ## v2.22.0 - 2024-06-25 | ||||
| ### Feature | ||||
| * ([#216](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/216)) [event bundle] exports  added for the event module | ||||
|  | ||||
| ### Traduction francophone | ||||
| * Exports sont ajoutés pour la module événement. | ||||
|  | ||||
| ## v2.21.0 - 2024-06-18 | ||||
| ### Feature | ||||
| * Add flash menu buttons in search results, to open directly a new calendar, or a new activity in an accompanying period | ||||
|   | ||||
| @@ -56,7 +56,7 @@ We strongly encourage you to initialize a git repository at this step, to track | ||||
|    cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json | ||||
|    # install chill and some dependencies | ||||
|    # TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released | ||||
|    symfony composer require chill-project/chill-bundles v3.0.0-alpha1 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev | ||||
|    symfony composer require chill-project/chill-bundles v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev | ||||
|  | ||||
| We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database. | ||||
| You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually). | ||||
| @@ -110,15 +110,14 @@ you can either: | ||||
|   .. code-block:: env | ||||
|  | ||||
|      ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm | ||||
|      # note: if you copy-paste the line above, the password will be "admin". | ||||
|  | ||||
| - add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env, | ||||
|   not the password in clear text). | ||||
|  | ||||
| - set up the jwt authentication bundle | ||||
|  | ||||
| Some environment variables are available for the JWT authentication bundle in the :code:`.env` file. You must also run the command | ||||
| :code:`symfony console lexik:jwt:generate-keypair` to generate some keys that will be stored in the paths set up in the :code:`JWT_SECRET_KEY` | ||||
| and the :code:`JWT_PUBLIC_KEY` env variables. This is only required for using the stored documents in Chill. | ||||
| Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.  | ||||
|  | ||||
| Prepare migrations and other tools | ||||
| ********************************** | ||||
| @@ -136,6 +135,8 @@ To continue the installation process, you will have to run migrations: | ||||
|    symfony console messenger:setup-transports | ||||
|    # prepare some views | ||||
|    symfony console chill:db:sync-views | ||||
|    # generate jwt token, required for some api features (webdav access, ...) | ||||
|    symfony console lexik:jwt:generate-keypair | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|   | ||||
| @@ -87,7 +87,6 @@ | ||||
|                             <li> | ||||
|                                 {% if bloc.type == 'user' %} | ||||
|                                     <span class="badge-user"> | ||||
|                                         hello | ||||
|                                         {{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }} | ||||
|                                     </span> | ||||
|                                 {% else %} | ||||
|   | ||||
| @@ -39,6 +39,7 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface | ||||
|         $loader->load('services/repositories.yaml'); | ||||
|         $loader->load('services/search.yaml'); | ||||
|         $loader->load('services/timeline.yaml'); | ||||
|         $loader->load('services/export.yaml'); | ||||
|     } | ||||
|  | ||||
|     /** (non-PHPdoc). | ||||
|   | ||||
| @@ -0,0 +1,110 @@ | ||||
| <?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\EventBundle\Export\Aggregator; | ||||
|  | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\MainBundle\Export\AggregatorInterface; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\Form\Extension\Core\Type\ChoiceType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
|  | ||||
| class EventDateAggregator implements AggregatorInterface | ||||
| { | ||||
|     private const CHOICES = [ | ||||
|         'by month' => 'month', | ||||
|         'by week' => 'week', | ||||
|         'by year' => 'year', | ||||
|     ]; | ||||
|  | ||||
|     private const DEFAULT_CHOICE = 'year'; | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data) | ||||
|     { | ||||
|         $order = null; | ||||
|  | ||||
|         switch ($data['frequency']) { | ||||
|             case 'month': | ||||
|                 $fmt = 'YYYY-MM'; | ||||
|  | ||||
|                 break; | ||||
|  | ||||
|             case 'week': | ||||
|                 $fmt = 'YYYY-IW'; | ||||
|  | ||||
|                 break; | ||||
|  | ||||
|             case 'year': | ||||
|                 $fmt = 'YYYY'; | ||||
|                 $order = 'DESC'; | ||||
|  | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 throw new \RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency'])); | ||||
|         } | ||||
|  | ||||
|         $qb->addSelect(sprintf("TO_CHAR(event.date, '%s') AS date_aggregator", $fmt)); | ||||
|         $qb->addGroupBy('date_aggregator'); | ||||
|         $qb->addOrderBy('date_aggregator', $order); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::EVENT; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|         $builder->add('frequency', ChoiceType::class, [ | ||||
|             'choices' => self::CHOICES, | ||||
|             'multiple' => false, | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['frequency' => self::DEFAULT_CHOICE]; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|         return static function ($value) use ($data): string { | ||||
|             if ('_header' === $value) { | ||||
|                 return 'by '.$data['frequency']; | ||||
|             } | ||||
|  | ||||
|             if (null === $value) { | ||||
|                 return ''; | ||||
|             } | ||||
|  | ||||
|             return match ($data['frequency']) { | ||||
|                 default => $value, | ||||
|             }; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data): array | ||||
|     { | ||||
|         return ['date_aggregator']; | ||||
|     } | ||||
|  | ||||
|     public function getTitle(): string | ||||
|     { | ||||
|         return 'Group event by date'; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| <?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\EventBundle\Export\Aggregator; | ||||
|  | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\EventBundle\Repository\EventTypeRepository; | ||||
| use Chill\MainBundle\Export\AggregatorInterface; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
|  | ||||
| class EventTypeAggregator implements AggregatorInterface | ||||
| { | ||||
|     final public const KEY = 'event_type_aggregator'; | ||||
|  | ||||
|     public function __construct(protected EventTypeRepository $eventTypeRepository, protected TranslatableStringHelperInterface $translatableStringHelper) {} | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data) | ||||
|     { | ||||
|         if (!\in_array('eventtype', $qb->getAllAliases(), true)) { | ||||
|             $qb->leftJoin('event.type', 'eventtype'); | ||||
|         } | ||||
|  | ||||
|         $qb->addSelect(sprintf('IDENTITY(event.type) AS %s', self::KEY)); | ||||
|         $qb->addGroupBy(self::KEY); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::EVENT; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|         // no form required for this aggregator | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): \Closure | ||||
|     { | ||||
|         return function (int|string|null $value): string { | ||||
|             if ('_header' === $value) { | ||||
|                 return 'Event type'; | ||||
|             } | ||||
|  | ||||
|             if (null === $value || '' === $value || null === $t = $this->eventTypeRepository->find($value)) { | ||||
|                 return ''; | ||||
|             } | ||||
|  | ||||
|             return $this->translatableStringHelper->localize($t->getName()); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data): array | ||||
|     { | ||||
|         return [self::KEY]; | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Group by event type'; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| <?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\EventBundle\Export\Aggregator; | ||||
|  | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\EventBundle\Repository\RoleRepository; | ||||
| use Chill\MainBundle\Export\AggregatorInterface; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
|  | ||||
| class RoleAggregator implements AggregatorInterface | ||||
| { | ||||
|     final public const KEY = 'part_role_aggregator'; | ||||
|  | ||||
|     public function __construct(protected RoleRepository $roleRepository, protected TranslatableStringHelperInterface $translatableStringHelper) {} | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data) | ||||
|     { | ||||
|         if (!\in_array('event_part', $qb->getAllAliases(), true)) { | ||||
|             $qb->leftJoin('event_part.role', 'role'); | ||||
|         } | ||||
|  | ||||
|         $qb->addSelect(sprintf('IDENTITY(event_part.role) AS %s', self::KEY)); | ||||
|         $qb->addGroupBy(self::KEY); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::EVENT_PARTICIPANTS; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|         // no form required for this aggregator | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): \Closure | ||||
|     { | ||||
|         return function (int|string|null $value): string { | ||||
|             if ('_header' === $value) { | ||||
|                 return 'Participant role'; | ||||
|             } | ||||
|  | ||||
|             if (null === $value || '' === $value || null === $r = $this->roleRepository->find($value)) { | ||||
|                 return ''; | ||||
|             } | ||||
|  | ||||
|             return $this->translatableStringHelper->localize($r->getName()); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data): array | ||||
|     { | ||||
|         return [self::KEY]; | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Group by participant role'; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										22
									
								
								src/Bundle/ChillEventBundle/Export/Declarations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Bundle/ChillEventBundle/Export/Declarations.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| <?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\EventBundle\Export; | ||||
|  | ||||
| /** | ||||
|  * This class declare constants used for the export framework. | ||||
|  */ | ||||
| abstract class Declarations | ||||
| { | ||||
|     final public const EVENT = 'event'; | ||||
|  | ||||
|     final public const EVENT_PARTICIPANTS = 'event_participants'; | ||||
| } | ||||
| @@ -0,0 +1,125 @@ | ||||
| <?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\EventBundle\Export\Export; | ||||
|  | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\EventBundle\Repository\ParticipationRepository; | ||||
| use Chill\EventBundle\Security\ParticipationVoter; | ||||
| use Chill\MainBundle\Export\ExportInterface; | ||||
| use Chill\MainBundle\Export\FormatterInterface; | ||||
| use Chill\MainBundle\Export\GroupedExportInterface; | ||||
| use Chill\PersonBundle\Entity\Person\PersonCenterHistory; | ||||
| use Doctrine\ORM\Query; | ||||
| use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Chill\PersonBundle\Export\Declarations as PersonDeclarations; | ||||
|  | ||||
| readonly class CountEventParticipations implements ExportInterface, GroupedExportInterface | ||||
| { | ||||
|     private bool $filterStatsByCenters; | ||||
|  | ||||
|     public function __construct( | ||||
|         private ParticipationRepository $participationRepository, | ||||
|         ParameterBagInterface $parameterBag, | ||||
|     ) { | ||||
|         $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) {} | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|         return [FormatterInterface::TYPE_TABULAR]; | ||||
|     } | ||||
|  | ||||
|     public function getDescription() | ||||
|     { | ||||
|         return 'Count participants to an event by various parameters.'; | ||||
|     } | ||||
|  | ||||
|     public function getGroup(): string | ||||
|     { | ||||
|         return 'Exports of events'; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|         if ('export_count_event_participants' !== $key) { | ||||
|             throw new \LogicException("the key {$key} is not used by this export"); | ||||
|         } | ||||
|  | ||||
|         return static fn ($value) => '_header' === $value ? 'Count event participants' : $value; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data) | ||||
|     { | ||||
|         return ['export_count_event_participants']; | ||||
|     } | ||||
|  | ||||
|     public function getResult($query, $data) | ||||
|     { | ||||
|         return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Count event participants'; | ||||
|     } | ||||
|  | ||||
|     public function getType(): string | ||||
|     { | ||||
|         return Declarations::EVENT_PARTICIPANTS; | ||||
|     } | ||||
|  | ||||
|     public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) | ||||
|     { | ||||
|         $centers = array_map(static fn ($el) => $el['center'], $acl); | ||||
|  | ||||
|         $qb = $this->participationRepository | ||||
|             ->createQueryBuilder('event_part') | ||||
|             ->join('event_part.person', 'person'); | ||||
|  | ||||
|         $qb->select('COUNT(event_part.id) as export_count_event_participants'); | ||||
|  | ||||
|         if ($this->filterStatsByCenters) { | ||||
|             $qb | ||||
|                 ->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) | ||||
|                     ' | ||||
|                     ) | ||||
|                 ) | ||||
|                 ->setParameter('authorized_centers', $centers); | ||||
|         } | ||||
|  | ||||
|         return $qb; | ||||
|     } | ||||
|  | ||||
|     public function requiredRole(): string | ||||
|     { | ||||
|         return ParticipationVoter::STATS; | ||||
|     } | ||||
|  | ||||
|     public function supportsModifiers() | ||||
|     { | ||||
|         return [ | ||||
|             Declarations::EVENT_PARTICIPANTS, | ||||
|             PersonDeclarations::PERSON_TYPE, | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										126
									
								
								src/Bundle/ChillEventBundle/Export/Export/CountEvents.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/Bundle/ChillEventBundle/Export/Export/CountEvents.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| <?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\EventBundle\Export\Export; | ||||
|  | ||||
| use Chill\EventBundle\Repository\EventRepository; | ||||
| use Chill\EventBundle\Security\EventVoter; | ||||
| use Chill\MainBundle\Export\ExportInterface; | ||||
| use Chill\MainBundle\Export\FormatterInterface; | ||||
| use Chill\MainBundle\Export\GroupedExportInterface; | ||||
| use Chill\PersonBundle\Entity\Person\PersonCenterHistory; | ||||
| use Doctrine\ORM\Query; | ||||
| use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\PersonBundle\Export\Declarations as PersonDeclarations; | ||||
|  | ||||
| readonly class CountEvents implements ExportInterface, GroupedExportInterface | ||||
| { | ||||
|     private bool $filterStatsByCenters; | ||||
|  | ||||
|     public function __construct( | ||||
|         private EventRepository $eventRepository, | ||||
|         ParameterBagInterface $parameterBag, | ||||
|     ) { | ||||
|         $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) {} | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|         return [FormatterInterface::TYPE_TABULAR]; | ||||
|     } | ||||
|  | ||||
|     public function getDescription() | ||||
|     { | ||||
|         return 'Count events by various parameters.'; | ||||
|     } | ||||
|  | ||||
|     public function getGroup(): string | ||||
|     { | ||||
|         return 'Exports of events'; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|         if ('export_count_event' !== $key) { | ||||
|             throw new \LogicException("the key {$key} is not used by this export"); | ||||
|         } | ||||
|  | ||||
|         return static fn ($value) => '_header' === $value ? 'Number of events' : $value; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data) | ||||
|     { | ||||
|         return ['export_count_event']; | ||||
|     } | ||||
|  | ||||
|     public function getResult($query, $data) | ||||
|     { | ||||
|         return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Count events'; | ||||
|     } | ||||
|  | ||||
|     public function getType(): string | ||||
|     { | ||||
|         return Declarations::EVENT; | ||||
|     } | ||||
|  | ||||
|     public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) | ||||
|     { | ||||
|         $centers = array_map(static fn ($el) => $el['center'], $acl); | ||||
|  | ||||
|         $qb = $this->eventRepository | ||||
|             ->createQueryBuilder('event') | ||||
|             ->leftJoin('event.participations', 'epart') | ||||
|             ->leftJoin('epart.person', 'person'); | ||||
|  | ||||
|         $qb->select('COUNT(event.id) as export_count_event'); | ||||
|  | ||||
|         if ($this->filterStatsByCenters) { | ||||
|             $qb | ||||
|                 ->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) | ||||
|                     ' | ||||
|                     ) | ||||
|                 ) | ||||
|                 ->setParameter('authorized_centers', $centers); | ||||
|         } | ||||
|  | ||||
|         return $qb; | ||||
|     } | ||||
|  | ||||
|     public function requiredRole(): string | ||||
|     { | ||||
|         return EventVoter::STATS; | ||||
|     } | ||||
|  | ||||
|     public function supportsModifiers() | ||||
|     { | ||||
|         return [ | ||||
|             Declarations::EVENT, | ||||
|             PersonDeclarations::PERSON_TYPE, | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,95 @@ | ||||
| <?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\EventBundle\Export\Filter; | ||||
|  | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\MainBundle\Export\FilterInterface; | ||||
| use Chill\MainBundle\Form\Type\PickRollingDateType; | ||||
| use Chill\MainBundle\Service\RollingDate\RollingDate; | ||||
| use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; | ||||
| use Doctrine\ORM\Query\Expr; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Contracts\Translation\TranslatorInterface; | ||||
|  | ||||
| class EventDateFilter implements FilterInterface | ||||
| { | ||||
|     public function __construct(protected TranslatorInterface $translator, private readonly RollingDateConverterInterface $rollingDateConverter) {} | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data) | ||||
|     { | ||||
|         $where = $qb->getDQLPart('where'); | ||||
|         $clause = $qb->expr()->between( | ||||
|             'event.date', | ||||
|             ':date_from', | ||||
|             ':date_to' | ||||
|         ); | ||||
|  | ||||
|         if ($where instanceof Expr\Andx) { | ||||
|             $where->add($clause); | ||||
|         } else { | ||||
|             $where = $qb->expr()->andX($clause); | ||||
|         } | ||||
|  | ||||
|         $qb->add('where', $where); | ||||
|         $qb->setParameter( | ||||
|             'date_from', | ||||
|             $this->rollingDateConverter->convert($data['date_from']) | ||||
|         ); | ||||
|         $qb->setParameter( | ||||
|             'date_to', | ||||
|             $this->rollingDateConverter->convert($data['date_to']) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::EVENT; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|         $builder | ||||
|             ->add('date_from', PickRollingDateType::class, [ | ||||
|                 'label' => 'Events after this date', | ||||
|             ]) | ||||
|             ->add('date_to', PickRollingDateType::class, [ | ||||
|                 'label' => 'Events before this date', | ||||
|             ]); | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|         return [ | ||||
|             'Filtered by date of event: only between %date_from% and %date_to%', | ||||
|             [ | ||||
|                 '%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), | ||||
|                 '%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'), | ||||
|             ], | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Filtered by event date'; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,94 @@ | ||||
| <?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\EventBundle\Export\Filter; | ||||
|  | ||||
| use Chill\EventBundle\Entity\EventType; | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\EventBundle\Repository\EventTypeRepository; | ||||
| use Chill\MainBundle\Export\ExportElementValidatedInterface; | ||||
| use Chill\MainBundle\Export\FilterInterface; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Bridge\Doctrine\Form\Type\EntityType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||||
|  | ||||
| class EventTypeFilter implements ExportElementValidatedInterface, FilterInterface | ||||
| { | ||||
|     public function __construct( | ||||
|         protected TranslatableStringHelperInterface $translatableStringHelper, | ||||
|         protected EventTypeRepository $eventTypeRepository | ||||
|     ) {} | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data) | ||||
|     { | ||||
|         $clause = $qb->expr()->in('event.type', ':selected_event_types'); | ||||
|  | ||||
|         $qb->andWhere($clause); | ||||
|         $qb->setParameter('selected_event_types', $data['types']); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::EVENT; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|         $builder->add('types', EntityType::class, [ | ||||
|             'choices' => $this->eventTypeRepository->findAllActive(), | ||||
|             'class' => EventType::class, | ||||
|             'choice_label' => fn (EventType $ety) => $this->translatableStringHelper->localize($ety->getName()), | ||||
|             'multiple' => true, | ||||
|             'expanded' => false, | ||||
|             'attr' => [ | ||||
|                 'class' => 'select2', | ||||
|             ], | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|         $typeNames = array_map( | ||||
|             fn (EventType $t): string => $this->translatableStringHelper->localize($t->getName()), | ||||
|             $this->eventTypeRepository->findBy(['id' => $data['types'] instanceof \Doctrine\Common\Collections\Collection ? $data['types']->toArray() : $data['types']]) | ||||
|         ); | ||||
|  | ||||
|         return ['Filtered by event type: only %list%', [ | ||||
|             '%list%' => implode(', ', $typeNames), | ||||
|         ]]; | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Filtered by event type'; | ||||
|     } | ||||
|  | ||||
|     public function validateForm($data, ExecutionContextInterface $context) | ||||
|     { | ||||
|         if (null === $data['types'] || 0 === \count($data['types'])) { | ||||
|             $context | ||||
|                 ->buildViolation('At least one type must be chosen') | ||||
|                 ->addViolation(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										94
									
								
								src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| <?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\EventBundle\Export\Filter; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Role; | ||||
| use Chill\EventBundle\Export\Declarations; | ||||
| use Chill\EventBundle\Repository\RoleRepository; | ||||
| use Chill\MainBundle\Export\ExportElementValidatedInterface; | ||||
| use Chill\MainBundle\Export\FilterInterface; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Bridge\Doctrine\Form\Type\EntityType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||||
|  | ||||
| class RoleFilter implements ExportElementValidatedInterface, FilterInterface | ||||
| { | ||||
|     public function __construct( | ||||
|         protected TranslatableStringHelperInterface $translatableStringHelper, | ||||
|         protected RoleRepository $roleRepository | ||||
|     ) {} | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data) | ||||
|     { | ||||
|         $clause = $qb->expr()->in('event_part.role', ':selected_part_roles'); | ||||
|  | ||||
|         $qb->andWhere($clause); | ||||
|         $qb->setParameter('selected_part_roles', $data['part_roles']); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::EVENT_PARTICIPANTS; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|         $builder->add('part_roles', EntityType::class, [ | ||||
|             'choices' => $this->roleRepository->findAllActive(), | ||||
|             'class' => Role::class, | ||||
|             'choice_label' => fn (Role $r) => $this->translatableStringHelper->localize($r->getName()), | ||||
|             'multiple' => true, | ||||
|             'expanded' => false, | ||||
|             'attr' => [ | ||||
|                 'class' => 'select2', | ||||
|             ], | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|         $roleNames = array_map( | ||||
|             fn (Role $r): string => $this->translatableStringHelper->localize($r->getName()), | ||||
|             $this->roleRepository->findBy(['id' => $data['part_roles'] instanceof \Doctrine\Common\Collections\Collection ? $data['part_roles']->toArray() : $data['part_roles']]) | ||||
|         ); | ||||
|  | ||||
|         return ['Filtered by participant roles: only %list%', [ | ||||
|             '%list%' => implode(', ', $roleNames), | ||||
|         ]]; | ||||
|     } | ||||
|  | ||||
|     public function getTitle() | ||||
|     { | ||||
|         return 'Filter by participant roles'; | ||||
|     } | ||||
|  | ||||
|     public function validateForm($data, ExecutionContextInterface $context) | ||||
|     { | ||||
|         if (null === $data['part_roles'] || 0 === \count($data['part_roles'])) { | ||||
|             $context | ||||
|                 ->buildViolation('At least one role must be chosen') | ||||
|                 ->addViolation(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -12,13 +12,57 @@ declare(strict_types=1); | ||||
| namespace Chill\EventBundle\Repository; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Role; | ||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | ||||
| use Doctrine\Persistence\ManagerRegistry; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelper; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\EntityRepository; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Doctrine\Persistence\ObjectRepository; | ||||
|  | ||||
| class RoleRepository extends ServiceEntityRepository | ||||
| readonly class RoleRepository implements ObjectRepository | ||||
| { | ||||
|     public function __construct(ManagerRegistry $registry) | ||||
|     private EntityRepository $repository; | ||||
|  | ||||
|     public function __construct(EntityManagerInterface $entityManager, private TranslatableStringHelper $translatableStringHelper) | ||||
|     { | ||||
|         parent::__construct($registry, Role::class); | ||||
|         $this->repository = $entityManager->getRepository(Role::class); | ||||
|     } | ||||
|  | ||||
|     public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder | ||||
|     { | ||||
|         return $this->repository->createQueryBuilder($alias, $indexBy); | ||||
|     } | ||||
|  | ||||
|     public function find($id) | ||||
|     { | ||||
|         return $this->repository->find($id); | ||||
|     } | ||||
|  | ||||
|     public function findAll(): array | ||||
|     { | ||||
|         return $this->repository->findAll(); | ||||
|     } | ||||
|  | ||||
|     public function findAllActive(): array | ||||
|     { | ||||
|         $roles = $this->repository->findBy(['active' => true]); | ||||
|  | ||||
|         usort($roles, fn (Role $a, Role $b) => $this->translatableStringHelper->localize($a->getName()) <=> $this->translatableStringHelper->localize($b->getName())); | ||||
|  | ||||
|         return $roles; | ||||
|     } | ||||
|  | ||||
|     public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array | ||||
|     { | ||||
|         return $this->repository->findBy($criteria, $orderBy, $limit, $offset); | ||||
|     } | ||||
|  | ||||
|     public function findOneBy(array $criteria) | ||||
|     { | ||||
|         return $this->repository->findOneBy($criteria); | ||||
|     } | ||||
|  | ||||
|     public function getClassName(): string | ||||
|     { | ||||
|         return Role::class; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,43 @@ | ||||
| <?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\EventBundle\Tests\Export; | ||||
|  | ||||
| use Chill\EventBundle\Export\Export\CountEventParticipations; | ||||
| use Doctrine\ORM\AbstractQuery; | ||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class CountEventParticipationsTest extends KernelTestCase | ||||
| { | ||||
|     private CountEventParticipations $countEventParticipations; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
|         self::bootKernel(); | ||||
|         $this->countEventParticipations = self::getContainer()->get(CountEventParticipations::class); | ||||
|     } | ||||
|  | ||||
|     public function testExecuteQuery(): void | ||||
|     { | ||||
|         $qb = $this->countEventParticipations->initiateQuery([], [], []) | ||||
|             ->setMaxResults(1); | ||||
|  | ||||
|         $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); | ||||
|  | ||||
|         self::assertIsArray($results, 'smoke test: test that the result is an array'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/Bundle/ChillEventBundle/Tests/Export/CountEventTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/Bundle/ChillEventBundle/Tests/Export/CountEventTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| <?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\EventBundle\Tests\Export; | ||||
|  | ||||
| use Chill\EventBundle\Export\Export\CountEvents; | ||||
| use Doctrine\ORM\AbstractQuery; | ||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class CountEventTest extends KernelTestCase | ||||
| { | ||||
|     private CountEvents $countEvents; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
|         self::bootKernel(); | ||||
|         $this->countEvents = self::getContainer()->get(CountEvents::class); | ||||
|     } | ||||
|  | ||||
|     public function testExecuteQuery(): void | ||||
|     { | ||||
|         $qb = $this->countEvents->initiateQuery([], [], []) | ||||
|             ->setMaxResults(1); | ||||
|  | ||||
|         $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); | ||||
|  | ||||
|         self::assertIsArray($results, 'smoke test: test that the result is an array'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| <?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 Export\aggregators; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Event; | ||||
| use Chill\EventBundle\Export\Aggregator\EventDateAggregator; | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class EventDateAggregatorTest extends AbstractAggregatorTest | ||||
| { | ||||
|     private $aggregator; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $this->aggregator = self::getContainer()->get(EventDateAggregator::class); | ||||
|     } | ||||
|  | ||||
|     public function getAggregator() | ||||
|     { | ||||
|         return $this->aggregator; | ||||
|     } | ||||
|  | ||||
|     public function getFormData(): array|\Generator | ||||
|     { | ||||
|         yield ['frequency' => 'YYYY']; | ||||
|         yield ['frequency' => 'YYYY-MM']; | ||||
|         yield ['frequency' => 'YYYY-IV']; | ||||
|     } | ||||
|  | ||||
|     public function getQueryBuilders(): array | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event.id') | ||||
|                 ->from(Event::class, 'event'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| <?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 Export\aggregators; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Event; | ||||
| use Chill\EventBundle\Export\Aggregator\EventTypeAggregator; | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class EventTypeAggregatorTest extends AbstractAggregatorTest | ||||
| { | ||||
|     private $aggregator; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $this->aggregator = self::getContainer()->get(EventTypeAggregator::class); | ||||
|     } | ||||
|  | ||||
|     public function getAggregator() | ||||
|     { | ||||
|         return $this->aggregator; | ||||
|     } | ||||
|  | ||||
|     public function getFormData(): array | ||||
|     { | ||||
|         return [ | ||||
|             [], | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public function getQueryBuilders(): array | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event.id') | ||||
|                 ->from(Event::class, 'event'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,63 @@ | ||||
| <?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 Export\aggregators; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Event; | ||||
| use Chill\EventBundle\Entity\Participation; | ||||
| use Chill\EventBundle\Export\Aggregator\RoleAggregator; | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class RoleAggregatorTest extends AbstractAggregatorTest | ||||
| { | ||||
|     private $aggregator; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $this->aggregator = self::getContainer()->get(RoleAggregator::class); | ||||
|     } | ||||
|  | ||||
|     public function getAggregator() | ||||
|     { | ||||
|         return $this->aggregator; | ||||
|     } | ||||
|  | ||||
|     public function getFormData(): array | ||||
|     { | ||||
|         return [ | ||||
|             [], | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public function getQueryBuilders(): array | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event.id') | ||||
|                 ->from(Event::class, 'event'), | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event_part') | ||||
|                 ->from(Participation::class, 'event_part'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,65 @@ | ||||
| <?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 Export\filters; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Event; | ||||
| use Chill\EventBundle\Export\Filter\EventDateFilter; | ||||
| use Chill\MainBundle\Service\RollingDate\RollingDate; | ||||
| use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; | ||||
| use Chill\MainBundle\Test\Export\AbstractFilterTest; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class EventDateFilterTest extends AbstractFilterTest | ||||
| { | ||||
|     private RollingDateConverterInterface $rollingDateConverter; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $this->rollingDateConverter = self::getContainer()->get(RollingDateConverterInterface::class); | ||||
|     } | ||||
|  | ||||
|     public function getFilter() | ||||
|     { | ||||
|         return new EventDateFilter($this->rollingDateConverter); | ||||
|     } | ||||
|  | ||||
|     public function getFormData() | ||||
|     { | ||||
|         return [ | ||||
|             [ | ||||
|                 'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), | ||||
|                 'date_to' => new RollingDate(RollingDate::T_TODAY), | ||||
|             ], | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public function getQueryBuilders(): array | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event.id') | ||||
|                 ->from(Event::class, 'event'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,76 @@ | ||||
| <?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 Export\filters; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Event; | ||||
| use Chill\EventBundle\Entity\EventType; | ||||
| use Chill\EventBundle\Export\Filter\EventTypeFilter; | ||||
| use Chill\MainBundle\Test\Export\AbstractFilterTest; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class EventTypeFilterTest extends AbstractFilterTest | ||||
| { | ||||
|     private EventTypeFilter $filter; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|         $this->filter = self::getContainer()->get(EventTypeFilter::class); | ||||
|     } | ||||
|  | ||||
|     public function getFilter(): EventTypeFilter|\Chill\MainBundle\Export\FilterInterface | ||||
|     { | ||||
|         return $this->filter; | ||||
|     } | ||||
|  | ||||
|     public function getFormData() | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         $array = $em->createQueryBuilder() | ||||
|             ->from(EventType::class, 'et') | ||||
|             ->select('et') | ||||
|             ->getQuery() | ||||
|             ->getResult(); | ||||
|  | ||||
|         $data = []; | ||||
|  | ||||
|         foreach ($array as $a) { | ||||
|             $data[] = [ | ||||
|                 'types' => new ArrayCollection([$a]), | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     public function getQueryBuilders() | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event.id') | ||||
|                 ->from(Event::class, 'event'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| <?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 Export\filters; | ||||
|  | ||||
| use Chill\EventBundle\Entity\Event; | ||||
| use Chill\EventBundle\Entity\Participation; | ||||
| use Chill\EventBundle\Entity\Role; | ||||
| use Chill\EventBundle\Export\Filter\RoleFilter; | ||||
| use Chill\MainBundle\Test\Export\AbstractFilterTest; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class RoleFilterTest extends AbstractFilterTest | ||||
| { | ||||
|     private RoleFilter $filter; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $this->filter = self::getContainer()->get(RoleFilter::class); | ||||
|     } | ||||
|  | ||||
|     public function getFilter() | ||||
|     { | ||||
|         return $this->filter; | ||||
|     } | ||||
|  | ||||
|     public function getFormData(): array | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         $array = $em->createQueryBuilder() | ||||
|             ->from(Role::class, 'r') | ||||
|             ->select('r') | ||||
|             ->getQuery() | ||||
|             ->setMaxResults(1) | ||||
|             ->getResult(); | ||||
|  | ||||
|         $data = []; | ||||
|  | ||||
|         foreach ($array as $a) { | ||||
|             $data[] = [ | ||||
|                 'roles' => new ArrayCollection([$a]), | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     public function getQueryBuilders() | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|  | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event.id') | ||||
|                 ->from(Event::class, 'event'), | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('event_part') | ||||
|                 ->from(Participation::class, 'event_part'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										41
									
								
								src/Bundle/ChillEventBundle/config/services/export.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/Bundle/ChillEventBundle/config/services/export.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| services: | ||||
|     _defaults: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|  | ||||
|     # indicators | ||||
|  | ||||
|     Chill\EventBundle\Export\Export\CountEvents: | ||||
|         tags: | ||||
|             - { name: chill.export, alias: 'count_events' } | ||||
|     Chill\EventBundle\Export\Export\CountEventParticipations: | ||||
|         tags: | ||||
|             - { name: chill.export, alias: 'count_event_participants' } | ||||
|  | ||||
|     # filters | ||||
|  | ||||
|     Chill\EventBundle\Export\Filter\EventDateFilter: | ||||
|         tags: | ||||
|             - { name: chill.export_filter, alias: 'event_date_filter' } | ||||
|  | ||||
|     Chill\EventBundle\Export\Filter\EventTypeFilter: | ||||
|         tags: | ||||
|             - { name: chill.export_filter, alias: 'event_type_filter' } | ||||
|  | ||||
|     Chill\EventBundle\Export\Filter\RoleFilter: | ||||
|         tags: | ||||
|             - { name: chill.export_filter, alias: 'role_filter' } | ||||
|  | ||||
|     # aggregators | ||||
|  | ||||
|     Chill\EventBundle\Export\Aggregator\EventTypeAggregator: | ||||
|         tags: | ||||
|             - { name: chill.export_aggregator, alias: event_type_aggregator } | ||||
|  | ||||
|     Chill\EventBundle\Export\Aggregator\EventDateAggregator: | ||||
|         tags: | ||||
|             - { name: chill.export_aggregator, alias: event_date_aggregator } | ||||
|  | ||||
|     Chill\EventBundle\Export\Aggregator\RoleAggregator: | ||||
|         tags: | ||||
|             - { name: chill.export_aggregator, alias: role_aggregator } | ||||
| @@ -12,4 +12,3 @@ services: | ||||
|         tags: | ||||
|             - { name: security.voter } | ||||
|             - { name: chill.role } | ||||
|  | ||||
|   | ||||
| @@ -80,9 +80,31 @@ Pick an event: Choisir un événement | ||||
| Pick a type of event: Choisir un type d'événement | ||||
| Pick a moderator: Choisir un animateur | ||||
|  | ||||
| # exports | ||||
| Select a format: Choisir un format | ||||
| Export: Exporter | ||||
|  | ||||
| Count events: Nombre d'événements | ||||
| Count events by various parameters.: Compte le nombre d'événements selon divers critères | ||||
| Exports of events: Exports d'événements | ||||
|  | ||||
| Filtered by event date: Filtrer par date d'événement | ||||
| 'Filtered by date of event: only between %date_from% and %date_to%': "Filtré par date d'événement: uniquement entre le %date_from% et le %date_to%" | ||||
| Events after this date: Événements après cette date | ||||
| Events before this date: Événements avant cette date | ||||
| Filtered by event type: Filtrer par type d'événement | ||||
| 'Filtered by event type: only %list%': "Filtré par type: uniquement %list%" | ||||
| Group event by date: Grouper par date d'événement | ||||
| Group by event type: Grouper par type d'événement | ||||
|  | ||||
| Count event participants: Nombre de participations | ||||
| Count participants to an event by various parameters.: Compte le nombre de participations selon divers critères | ||||
| Exports of event participants: Exports de participations | ||||
| 'Filtered by participant roles: only %list%': "Filtré par rôles de participation: uniquement %list%" | ||||
| Filter by participant roles: Filtrer par rôles de participation | ||||
| Part roles: Rôles de participation | ||||
| Group by participant role: Grouper par rôle de participation | ||||
|  | ||||
|  | ||||
| Events configuration: Configuration des événements | ||||
| Events configuration menu: Menu des événements | ||||
|   | ||||
| @@ -0,0 +1,40 @@ | ||||
| <?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\MainBundle\Command; | ||||
|  | ||||
| use Chill\MainBundle\Service\Import\AddressReferenceLU; | ||||
| use Symfony\Component\Console\Command\Command; | ||||
| use Symfony\Component\Console\Input\InputInterface; | ||||
| use Symfony\Component\Console\Output\OutputInterface; | ||||
|  | ||||
| class LoadAddressesLUFromBDAddressCommand extends Command | ||||
| { | ||||
|     protected static $defaultDescription = 'Import LUX addresses from BD addresses (see https://data.public.lu/fr/datasets/adresses-georeferencees-bd-adresses/)'; | ||||
|  | ||||
|     public function __construct( | ||||
|         private readonly AddressReferenceLU $addressImporter, | ||||
|     ) { | ||||
|         parent::__construct(); | ||||
|     } | ||||
|  | ||||
|     protected function configure() | ||||
|     { | ||||
|         $this->setName('chill:main:address-ref-lux'); | ||||
|     } | ||||
|  | ||||
|     protected function execute(InputInterface $input, OutputInterface $output): int | ||||
|     { | ||||
|         $this->addressImporter->import(); | ||||
|  | ||||
|         return Command::SUCCESS; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,95 @@ | ||||
| <?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\MainBundle\Service\Import; | ||||
|  | ||||
| use League\Csv\Reader; | ||||
| use League\Csv\Statement; | ||||
| use Symfony\Contracts\HttpClient\HttpClientInterface; | ||||
|  | ||||
| class AddressReferenceLU | ||||
| { | ||||
|     private const RELEASE = 'https://data.public.lu/fr/datasets/r/5cadc5b8-6a7d-4283-87bc-f9e58dd771f7'; | ||||
|  | ||||
|     public function __construct(private readonly HttpClientInterface $client, private readonly AddressReferenceBaseImporter $addressBaseImporter, private readonly PostalCodeBaseImporter $postalCodeBaseImporter, private readonly AddressToReferenceMatcher $addressToReferenceMatcher) {} | ||||
|  | ||||
|     public function import(): void | ||||
|     { | ||||
|         $downloadUrl = self::RELEASE; | ||||
|  | ||||
|         $response = $this->client->request('GET', $downloadUrl); | ||||
|  | ||||
|         if (200 !== $response->getStatusCode()) { | ||||
|             throw new \Exception('Could not download CSV: '.$response->getStatusCode()); | ||||
|         } | ||||
|  | ||||
|         $file = tmpfile(); | ||||
|  | ||||
|         foreach ($this->client->stream($response) as $chunk) { | ||||
|             fwrite($file, $chunk->getContent()); | ||||
|         } | ||||
|  | ||||
|         fseek($file, 0); | ||||
|  | ||||
|         $csv = Reader::createFromStream($file); | ||||
|         $csv->setDelimiter(';'); | ||||
|         $csv->setHeaderOffset(0); | ||||
|  | ||||
|         $this->process_postal_code($csv); | ||||
|  | ||||
|         $this->process_address($csv); | ||||
|  | ||||
|         $this->addressToReferenceMatcher->checkAddressesMatchingReferences(); | ||||
|  | ||||
|         fclose($file); | ||||
|     } | ||||
|  | ||||
|     private function process_address(Reader $csv): void | ||||
|     { | ||||
|         $stmt = Statement::create()->process($csv); | ||||
|         foreach ($stmt as $record) { | ||||
|             $this->addressBaseImporter->importAddress( | ||||
|                 $record['id_geoportail'], | ||||
|                 $record['code_postal'], | ||||
|                 $record['code_postal'], | ||||
|                 $record['rue'], | ||||
|                 $record['numero'], | ||||
|                 'bd-addresses.lux', | ||||
|                 (float) $record['lat_wgs84'], | ||||
|                 (float) $record['lon_wgs84'], | ||||
|                 4326 | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         $this->addressBaseImporter->finalize(); | ||||
|     } | ||||
|  | ||||
|     private function process_postal_code(Reader $csv): void | ||||
|     { | ||||
|         $stmt = Statement::create()->process($csv); | ||||
|         $arr_postal_codes = []; | ||||
|         foreach ($stmt as $record) { | ||||
|             if (false === \array_key_exists($record['code_postal'], $arr_postal_codes)) { | ||||
|                 $this->postalCodeBaseImporter->importCode( | ||||
|                     'LU', | ||||
|                     trim((string) $record['localite']), | ||||
|                     trim((string) $record['code_postal']), | ||||
|                     trim((string) $record['code_postal']), | ||||
|                     'bd-addresses.lux', | ||||
|                     (float) $record['lat_wgs84'], | ||||
|                     (float) $record['lon_wgs84'], | ||||
|                     4326 | ||||
|                 ); | ||||
|                 $arr_postal_codes[$record['code_postal']] = 1; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -59,6 +59,12 @@ services: | ||||
|         tags: | ||||
|             - { name: console.command } | ||||
|  | ||||
|     Chill\MainBundle\Command\LoadAddressesLUFromBDAddressCommand: | ||||
|         autoconfigure: true | ||||
|         autowire: true | ||||
|         tags: | ||||
|             - { name: console.command } | ||||
|  | ||||
|     Chill\MainBundle\Command\ExecuteCronJobCommand: | ||||
|         autoconfigure: true | ||||
|         autowire: true | ||||
|   | ||||
		Reference in New Issue
	
	Block a user