diff --git a/.changes/unreleased/Feature-20231018-113825.yaml b/.changes/unreleased/Feature-20231018-113825.yaml new file mode 100644 index 000000000..f8922b356 --- /dev/null +++ b/.changes/unreleased/Feature-20231018-113825.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[export] Add a filter "grouping accompanying period by opening date" and "trouping + accompanying period by closing date"' +time: 2023-10-18T11:38:25.80362952+02:00 +custom: + Issue: "172" diff --git a/.changes/unreleased/Feature-20231018-133203.yaml b/.changes/unreleased/Feature-20231018-133203.yaml new file mode 100644 index 000000000..f839ebcf3 --- /dev/null +++ b/.changes/unreleased/Feature-20231018-133203.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[export] add a filter and aggregator on accompanying period work: group/filter + by handling third party' +time: 2023-10-18T13:32:03.565201495+02:00 +custom: + Issue: "172" diff --git a/.changes/unreleased/Feature-20231018-160235.yaml b/.changes/unreleased/Feature-20231018-160235.yaml new file mode 100644 index 000000000..87fe1de0c --- /dev/null +++ b/.changes/unreleased/Feature-20231018-160235.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[export] add a filter and aggregator on activites: group/filter activities + by people participating to the exchange' +time: 2023-10-18T16:02:35.99265091+02:00 +custom: + Issue: "172" diff --git a/.changes/unreleased/Feature-20231018-164927.yaml b/.changes/unreleased/Feature-20231018-164927.yaml new file mode 100644 index 000000000..cf0be6fef --- /dev/null +++ b/.changes/unreleased/Feature-20231018-164927.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[export] add a grouping on accompanying period export: group by activity type + associated to at least one activity within the accompanying period' +time: 2023-10-18T16:49:27.567166953+02:00 +custom: + Issue: "172" diff --git a/.changes/unreleased/Feature-20231019-140357.yaml b/.changes/unreleased/Feature-20231019-140357.yaml new file mode 100644 index 000000000..2ef33a4a5 --- /dev/null +++ b/.changes/unreleased/Feature-20231019-140357.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: '[export] sort filters and aggregators by title' +time: 2023-10-19T14:03:57.435338127+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/Fixed-20231019-114350.yaml b/.changes/unreleased/Fixed-20231019-114350.yaml new file mode 100644 index 000000000..dbb6b4166 --- /dev/null +++ b/.changes/unreleased/Fixed-20231019-114350.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: '[export] fix date range selection on filter and grouping "by status of the + course at date", on accompanying periods' +time: 2023-10-19T11:43:50.455829748+02:00 +custom: + Issue: "177" diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php new file mode 100644 index 000000000..cdb66bb77 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php @@ -0,0 +1,122 @@ +add('after_date', PickRollingDateType::class, [ + 'required' => false, + 'label' => 'export.aggregator.acp.by_activity_type.after_date', + ]) + ->add('before_date', PickRollingDateType::class, [ + 'required' => false, + 'label' => 'export.aggregator.acp.by_activity_type.before_date', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'before_date' => null, + 'after_date' => null, + ]; + } + + public function getLabels($key, array $values, mixed $data) + { + return function (null|int|string $value): string { + if ('_header' === $value) { + return 'export.aggregator.acp.by_activity_type.activity_type'; + } + + if ('' === $value || null === $value || null === $activityType = $this->activityTypeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($activityType->getName()); + }; + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_actype_id']; + } + + public function getTitle() + { + return 'export.aggregator.acp.by_activity_type.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + // we make a left join, with acp having at least one activity of the given type + $exists = 'EXISTS (SELECT 1 FROM '.Activity::class." {$p}_activity WHERE {$p}_activity.accompanyingPeriod = acp AND {$p}_activity.activityType = {$p}_activity_type"; + + if (null !== $data['after_date']) { + $exists .= " AND {$p}_activity.date > :{$p}_after_date"; + $qb->setParameter("{$p}_after_date", $this->rollingDateConverter->convert($data['after_date'])); + } + + if (null !== $data['before_date']) { + $exists .= " AND {$p}_activity.date < :{$p}_before_date"; + $qb->setParameter("{$p}_before_date", $this->rollingDateConverter->convert($data['before_date'])); + } + + $exists .= ')'; + + $qb->leftJoin( + ActivityType::class, + "{$p}_activity_type", + Join::WITH, + $exists + ); + + $qb + ->addSelect("{$p}_activity_type.id AS {$p}_actype_id") + ->addGroupBy("{$p}_actype_id"); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php index f7315140e..9d9e5ed61 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php @@ -73,7 +73,6 @@ class DateAggregator implements AggregatorInterface 'choices' => self::CHOICES, 'multiple' => false, 'expanded' => true, - 'empty_data' => self::DEFAULT_CHOICE, ]); } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php new file mode 100644 index 000000000..8459741c5 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php @@ -0,0 +1,78 @@ +labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons'); + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_pid']; + } + + public function getTitle() + { + return 'export.aggregator.activity.by_persons.Group activity by persons'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + $qb + ->leftJoin('activity.persons', "{$p}_p") + ->addSelect("{$p}_p.id AS {$p}_pid") + ->addGroupBy("{$p}_pid"); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } +} diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php new file mode 100644 index 000000000..8bdefeb24 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php @@ -0,0 +1,87 @@ +expr()->orX(); + + foreach (array_values($data['accepted_persons']) as $key => $person) { + $orX->add($qb->expr()->isMemberOf(":{$p}_p_{$key}", 'activity.persons')); + $qb->setParameter(":{$p}_p_{$key}", $person); + } + + $qb->andWhere($orX); + } + + public function applyOn() + { + return Declarations::ACTIVITY; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_persons', PickPersonDynamicType::class, [ + 'multiple' => true, + 'label' => 'export.filter.activity.by_persons.persons taking part on the activity', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'accepted_persons' => [], + ]; + } + + public function describeAction($data, $format = 'string') + { + $users = []; + + foreach ($data['accepted_persons'] as $u) { + $users[] = $this->personRender->renderString($u, []); + } + + return ['export.filter.activity.by_persons.Filtered activity by persons: only %persons%', [ + '%persons%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'export.filter.activity.by_persons.Filter activity by persons'; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByActivityTypeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByActivityTypeAggregatorTest.php new file mode 100644 index 000000000..2d78585f7 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByActivityTypeAggregatorTest.php @@ -0,0 +1,87 @@ +rollingDateConverter = self::$container->get(RollingDateConverterInterface::class); + $this->activityTypeRepository = self::$container->get(ActivityTypeRepositoryInterface::class); + $this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class); + } + + public function getAggregator() + { + return new ByActivityTypeAggregator( + $this->rollingDateConverter, + $this->activityTypeRepository, + $this->translatableStringHelper, + ); + } + + public function getFormData() + { + return [ + [ + 'after_date' => null, + 'before_date' => null, + ], + [ + 'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'before_date' => null, + ], + [ + 'after_date' => null, + 'before_date' => new RollingDate(RollingDate::T_TODAY), + ], + [ + 'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'before_date' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(distinct acp.id)') + ->from(AccompanyingPeriod::class, 'acp'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonsAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonsAggregatorTest.php new file mode 100644 index 000000000..b067e308d --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonsAggregatorTest.php @@ -0,0 +1,60 @@ +labelPersonHelper = self::$container->get(LabelPersonHelper::class); + } + + public function getAggregator() + { + return new PersonsAggregator($this->labelPersonHelper); + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'), + ]; + } +} diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonsFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonsFilterTest.php new file mode 100644 index 000000000..cefc226e7 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/PersonsFilterTest.php @@ -0,0 +1,72 @@ +personRender = self::$container->get(PersonRenderInterface::class); + } + + public function getFilter() + { + return new PersonsFilter($this->personRender); + } + + public function getFormData() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $persons = $em->createQuery('SELECT p FROM '.Person::class.' p ') + ->setMaxResults(2) + ->getResult(); + + self::ensureKernelShutdown(); + + return [ + [ + 'accepted_persons' => $persons, + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + yield $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity'); + + self::ensureKernelShutdown(); + } +} diff --git a/src/Bundle/ChillActivityBundle/config/services/export.yaml b/src/Bundle/ChillActivityBundle/config/services/export.yaml index 09a5227eb..24f10d399 100644 --- a/src/Bundle/ChillActivityBundle/config/services/export.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/export.yaml @@ -200,3 +200,15 @@ services: Chill\ActivityBundle\Export\Aggregator\SentReceivedAggregator: tags: - { name: chill.export_aggregator, alias: activity_sentreceived_aggregator } + + Chill\ActivityBundle\Export\Filter\PersonsFilter: + tags: + - { name: chill.export_filter, alias: activity_by_persons_filter } + + Chill\ActivityBundle\Export\Aggregator\PersonsAggregator: + tags: + - { name: chill.export_aggregator, alias: activity_by_persons_aggregator } + + Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByActivityTypeAggregator: + tags: + - { name: chill.export_aggregator, alias: acp_by_activity_type_aggregator } diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 123a0d075..a7112b534 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -362,7 +362,7 @@ export: Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant 'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%' course_having_activity_between_date: - Title: Filtre les parcours ayant reçu un échange entre deux dates + Title: Filtrer les parcours ayant reçu un échange entre deux dates Receiving an activity after: Ayant reçu un échange après le Receiving an activity before: Ayant reçu un échange avant le acp_by_activity_type: @@ -372,13 +372,23 @@ export: Implied in an activity before this date: Impliqué dans un échange avant cette date Activity reasons for those activities: Sujets de ces échanges if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte - title: Filtrer les personnes ayant été associés à un échange au cours de la période + title: Filtrer les usagers ayant été associés à un échange au cours de la période date mismatch: La date de fin de la période doit être supérieure à la date du début by_creator_scope: Filter activity by user scope: Filtrer les échanges par service du créateur de l'échange 'Filtered activity by user scope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" + by_persons: + Filter activity by persons: Filtrer les échanges par usager participant + 'Filtered activity by persons: only %persons%': 'Échanges filtrés par usagers participants: seulement %persons%' + persons taking part on the activity: Usagers participants à l'échange aggregator: + acp: + by_activity_type: + title: Grouper les parcours par type d'échange + after_date: Uniquement échanges après cette date + before_date: Uniquement échanges avant cette date + activity_type: Types d'échange activity: by_sent_received: Sent or received: Envoyé ou reçu @@ -400,6 +410,9 @@ export: by_creator_job: Group activity by creator job: Grouper les échanges par service du créateur de l'échange Calc date: Date de calcul du service du créateur de l'échange + by_persons: + Group activity by persons: Grouper les échanges par usager participant + Persons: Usagers participants generic_doc: filter: diff --git a/src/Bundle/ChillMainBundle/Export/ExportManager.php b/src/Bundle/ChillMainBundle/Export/ExportManager.php index 8cb69bd63..067cc72e9 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportManager.php +++ b/src/Bundle/ChillMainBundle/Export/ExportManager.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -54,20 +55,17 @@ class ExportManager */ private array $formatters = []; - private readonly string|\Stringable|\Symfony\Component\Security\Core\User\UserInterface $user; - public function __construct( private readonly LoggerInterface $logger, private readonly AuthorizationCheckerInterface $authorizationChecker, private readonly AuthorizationHelperInterface $authorizationHelper, - TokenStorageInterface $tokenStorage, + private readonly TokenStorageInterface $tokenStorage, iterable $exports, iterable $aggregators, iterable $filters // iterable $formatters, // iterable $exportElementProvider ) { - $this->user = $tokenStorage->getToken()->getUser(); $this->exports = iterator_to_array($exports); $this->aggregators = iterator_to_array($aggregators); $this->filters = iterator_to_array($filters); @@ -91,20 +89,24 @@ class ExportManager * * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias */ - public function &getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): iterable + public function getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array { if ($export instanceof DirectExportInterface) { - return; + return []; } + $filters = []; + foreach ($this->filters as $alias => $filter) { if ( \in_array($filter->applyOn(), $export->supportsModifiers(), true) && $this->isGrantedForElement($filter, $export, $centers) ) { - yield $alias => $filter; + $filters[$alias] = $filter; } } + + return $filters; } /** @@ -112,22 +114,26 @@ class ExportManager * * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array * - * @return iterable|null a \Generator that contains aggretagors. The key is the filter's alias + * @return array an array that contains aggregators. The key is the filter's alias */ - public function &getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): ?iterable + public function getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array { if ($export instanceof ListInterface || $export instanceof DirectExportInterface) { - return; + return []; } + $aggregators = []; + foreach ($this->aggregators as $alias => $aggregator) { if ( \in_array($aggregator->applyOn(), $export->supportsModifiers(), true) && $this->isGrantedForElement($aggregator, $export, $centers) ) { - yield $alias => $aggregator; + $aggregators[$alias] = $aggregator; } } + + return $aggregators; } public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void @@ -347,6 +353,17 @@ class ExportManager return $this->filters[$alias]; } + public function getAllFilters(): array + { + $filters = []; + + foreach ($this->filters as $alias => $filter) { + $filters[$alias] = $filter; + } + + return $filters; + } + /** * get all filters. * @@ -452,7 +469,7 @@ class ExportManager if (null === $centers || [] === $centers) { // we want to try if at least one center is reachable return [] !== $this->authorizationHelper->getReachableCenters( - $this->user, + $this->tokenStorage->getToken()->getUser(), $role ); } @@ -484,11 +501,17 @@ class ExportManager { $r = []; + $user = $this->tokenStorage->getToken()->getUser(); + + if (!$user instanceof User) { + return []; + } + foreach ($centers as $center) { $r[] = [ 'center' => $center, 'circles' => $this->authorizationHelper->getReachableScopes( - $this->user, + $user, $element->requiredRole(), $center ), diff --git a/src/Bundle/ChillMainBundle/Export/SortExportElement.php b/src/Bundle/ChillMainBundle/Export/SortExportElement.php new file mode 100644 index 000000000..6228109ed --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/SortExportElement.php @@ -0,0 +1,37 @@ + $elements + */ + public function sortFilters(array &$elements): void + { + uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + } + + /** + * @param array $elements + */ + public function sortAggregators(array &$elements): void + { + uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php index 324b5d8d7..e5d0887f3 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Form\Type\Export; use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\SortExportElement; use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -28,15 +29,7 @@ class ExportType extends AbstractType final public const PICK_FORMATTER_KEY = 'pick_formatter'; - /** - * @var ExportManager - */ - protected $exportManager; - - public function __construct(ExportManager $exportManager) - { - $this->exportManager = $exportManager; - } + public function __construct(private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -58,12 +51,12 @@ class ExportType extends AbstractType if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { // add filters $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); + $this->sortExportElement->sortFilters($filters); $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); foreach ($filters as $alias => $filter) { $filterBuilder->add($alias, FilterType::class, [ - 'filter_alias' => $alias, - 'export_manager' => $this->exportManager, + 'filter' => $filter, 'label' => $filter->getTitle(), 'constraints' => [ new ExportElementConstraint(['element' => $filter]), @@ -76,6 +69,7 @@ class ExportType extends AbstractType // add aggregators $aggregators = $this->exportManager ->getAggregatorsApplyingOn($export, $options['picked_centers']); + $this->sortExportElement->sortAggregators($aggregators); $aggregatorBuilder = $builder->create( self::AGGREGATOR_KEY, FormType::class, diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php index d4f897430..bcf842e73 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Form\Type\Export; +use Chill\MainBundle\Export\FilterInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -25,8 +26,7 @@ class FilterType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { - $exportManager = $options['export_manager']; - $filter = $exportManager->getFilter($options['filter_alias']); + $filter = $options['filter']; $builder ->add(self::ENABLED_FIELD, CheckboxType::class, [ @@ -46,8 +46,9 @@ class FilterType extends AbstractType public function configureOptions(OptionsResolver $resolver) { - $resolver->setRequired('filter_alias') - ->setRequired('export_manager') + $resolver + ->setRequired('filter') + ->setAllowedTypes('filter', [FilterInterface::class]) ->setDefault('compound', true) ->setDefault('error_bubbling', false); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php b/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php index c23e0fa81..8ceb08578 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php @@ -34,6 +34,7 @@ class PickRollingDateType extends AbstractType ), 'multiple' => false, 'expanded' => false, + 'required' => $options['required'], 'label' => 'rolling_date.roll_movement', ]) ->add('fixedDate', ChillDateType::class, [ @@ -57,7 +58,10 @@ class PickRollingDateType extends AbstractType 'constraints' => [ new Callback($this->validate(...)), ], + 'required' => true, ]); + + $resolver->setAllowedTypes('required', 'bool'); } public function validate($data, ExecutionContextInterface $context, $payload): void diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/new.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/new.html.twig index 8ef4c3ad7..82b3e0c0c 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/new.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/new.html.twig @@ -1,5 +1,5 @@ {# - * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, + * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, / * * This program is free software: you can redistribute it and/or modify @@ -36,84 +36,84 @@ {% block content %}
- + {{ include('@ChillMain/Export/_breadcrumb.html.twig') }} - +

{{ export.title|trans }}

- +

{{ export.description|trans }}

- + {{ form_start(form) }} - + {% if form.children.export.children.filters is defined %} {% if form.children.export.children.filters is not empty%} - +

{{ 'Filters'| trans }}

- + {{ form_errors(form.children.export.children.filters) }} - +
{% for filter_form in form.children.export.children.filters %}
- + {{ form_widget(filter_form.enabled, { 'label': filter_form.vars.label, 'label_attr': { 'class': 'h6' }, 'attr': { 'data-display-target': filter_form.vars.id } }) }} - +
{{ form_widget(filter_form.form) }} {{ form_errors(filter_form) }}
- +
{% endfor %}
- + {% else %} {# render the children, to mark the widget as 'rendered' #} {{ form_widget(form.children.export.children.filters) }} {% endif %} {% endif %} - + {% if form.children.export.children.aggregators is defined %} {% if form.children.export.children.aggregators is not empty %} - +

{{ 'Aggregators'| trans }}

- +
{% for aggregator_form in form.children.export.children.aggregators %}
- + {{ form_widget(aggregator_form.enabled, { 'label': aggregator_form.vars.label, 'label_attr': { 'class': 'h6' }, 'attr': { 'data-display-target': aggregator_form.vars.id } }) }} - +
{{ form_widget(aggregator_form.form) }} {{ form_errors(aggregator_form) }}
- +
{% endfor %}
- + {% else %} {# render the children, to mark the widget as 'rendered' #} {{ form_widget(form.children.export.children.aggregators) }} {% endif %} {% endif %} - - + + {% if form.children.export.children.export.children|length > 0 %} - + @@ -123,12 +123,12 @@ {{ form_widget(form.children.export.children.export) }} - + {% else %} {# render the children, to mark the widget as 'rendered' #} {{ form_widget(form.children.export.children.export) }} {% endif %} - + {% if form.children.export.children.pick_formatter is defined %}

@@ -140,7 +140,7 @@ {{ form_row(form.children.export.children.pick_formatter.children.alias, { 'label' : 'Formatter' }) }}

{% endif %} - +

{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' }, 'label' : 'Go to formatter options' } ) }}

{{ form_end(form) }} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/SortExportElementTest.php b/src/Bundle/ChillMainBundle/Tests/Export/SortExportElementTest.php new file mode 100644 index 000000000..e7dbaf8fb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/SortExportElementTest.php @@ -0,0 +1,193 @@ +sortExportElement = new SortExportElement($this->makeTranslator()); + } + + public function testSortFilterRealData(): void + { + self::bootKernel(); + + $sorter = self::$container->get(SortExportElement::class); + $translator = self::$container->get(TranslatorInterface::class); + $exportManager = self::$container->get(ExportManager::class); + $filters = $exportManager->getAllFilters(); + + $sorter->sortFilters($filters); + + $previousName = null; + foreach ($filters as $filter) { + if (null === $previousName) { + $previousName = $translator->trans($filter->getTitle()); + continue; + } + + $current = $translator->trans($filter->getTitle()); + if ($current === $previousName) { + continue; + } + + self::assertEquals(-1, $previousName <=> $current, sprintf("comparing '%s' and '%s'", $previousName, $current)); + $previousName = $current; + } + } + + public function testSortAggregator(): void + { + $aggregators = [ + 'foo' => $a = $this->makeAggregator('a'), + 'zop' => $q = $this->makeAggregator('q'), + 'bar' => $c = $this->makeAggregator('c'), + 'baz' => $b = $this->makeAggregator('b'), + ]; + + $this->sortExportElement->sortAggregators($aggregators); + + self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($aggregators)); + self::assertSame($a, $aggregators['foo']); + self::assertSame($b, $aggregators['baz']); + self::assertSame($c, $aggregators['bar']); + self::assertSame($q, $aggregators['zop']); + } + + public function testSortFilter(): void + { + $filters = [ + 'foo' => $a = $this->makeFilter('a'), + 'zop' => $q = $this->makeFilter('q'), + 'bar' => $c = $this->makeFilter('c'), + 'baz' => $b = $this->makeFilter('b'), + ]; + + $this->sortExportElement->sortFilters($filters); + + self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($filters)); + self::assertSame($a, $filters['foo']); + self::assertSame($b, $filters['baz']); + self::assertSame($c, $filters['bar']); + self::assertSame($q, $filters['zop']); + } + + private function makeTranslator(): TranslatorInterface + { + return new class () implements TranslatorInterface { + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null) + { + return $id; + } + + public function getLocale(): string + { + return 'en'; + } + }; + } + + private function makeAggregator(string $title): AggregatorInterface + { + return new class ($title) implements AggregatorInterface { + public function __construct(private readonly string $title) {} + + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array + { + return []; + } + + public function getLabels($key, array $values, mixed $data) + { + return fn ($v) => $v; + } + + public function getQueryKeys($data) + { + return []; + } + + public function getTitle() + { + return $this->title; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) {} + + public function applyOn() + { + return []; + } + }; + } + + private function makeFilter(string $title): FilterInterface + { + return new class ($title) implements FilterInterface { + public function __construct(private readonly string $title) {} + + public function getTitle() + { + return $this->title; + } + + public function buildForm(FormBuilderInterface $builder) {} + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, $format = 'string') + { + return ['a', []]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) {} + + public function applyOn() + { + return []; + } + }; + } +} diff --git a/src/Bundle/ChillMainBundle/config/services/export.yaml b/src/Bundle/ChillMainBundle/config/services/export.yaml index 6bae6f9c0..ece7ae902 100644 --- a/src/Bundle/ChillMainBundle/config/services/export.yaml +++ b/src/Bundle/ChillMainBundle/config/services/export.yaml @@ -54,3 +54,5 @@ services: - { name: chill.export_formatter, alias: 'csv_pivoted_list' } Chill\MainBundle\Export\AccompanyingCourseExportHelper: ~ + + Chill\MainBundle\Export\SortExportElement: ~ diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php new file mode 100644 index 000000000..ce96c40c9 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php @@ -0,0 +1,88 @@ +add('frequency', ChoiceType::class, [ + 'choices' => array_combine( + array_map(fn (DateGroupingChoiceEnum $c) => 'export.enum.frequency.'.$c->value, DateGroupingChoiceEnum::cases()), + array_map(fn (DateGroupingChoiceEnum $c) => $c->value, DateGroupingChoiceEnum::cases()), + ), + 'label' => 'export.aggregator.course.by_closing_date.frequency', + 'multiple' => false, + 'expanded' => true, + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'frequency' => 'year', + ]; + } + + public function getLabels($key, array $values, mixed $data) + { + return function (null|string $value): string { + if ('_header' === $value) { + return 'export.aggregator.course.by_closing_date.header'; + } + + return (string) $value; + }; + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_closing_date']; + } + + public function getTitle() + { + return 'export.aggregator.course.by_closing_date.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + $qb->addSelect(sprintf("TO_CHAR(acp.closingDate, '%s') AS {$p}_closing_date", $data['frequency'])); + $qb->addGroupBy("{$p}_closing_date"); + $qb->addOrderBy("{$p}_closing_date", 'DESC'); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php new file mode 100644 index 000000000..bf71d04d8 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php @@ -0,0 +1,88 @@ +add('frequency', ChoiceType::class, [ + 'choices' => array_combine( + array_map(fn (DateGroupingChoiceEnum $c) => 'export.enum.frequency.'.$c->value, DateGroupingChoiceEnum::cases()), + array_map(fn (DateGroupingChoiceEnum $c) => $c->value, DateGroupingChoiceEnum::cases()), + ), + 'label' => 'export.aggregator.course.by_opening_date.frequency', + 'multiple' => false, + 'expanded' => true, + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'frequency' => 'year', + ]; + } + + public function getLabels($key, array $values, mixed $data) + { + return function (null|string $value): string { + if ('_header' === $value) { + return 'export.aggregator.course.by_opening_date.header'; + } + + return (string) $value; + }; + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_opening_date']; + } + + public function getTitle() + { + return 'export.aggregator.course.by_opening_date.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + $qb->addSelect(sprintf("TO_CHAR(acp.openingDate, '%s') AS {$p}_opening_date", $data['frequency'])); + $qb->addGroupBy("{$p}_opening_date"); + $qb->addOrderBy("{$p}_opening_date", 'DESC'); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php index dd5c26371..a9439a63f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php @@ -49,7 +49,7 @@ final readonly class StepAggregator implements AggregatorInterface $qb->expr()->lte(self::A.'.startDate', ':'.self::P), $qb->expr()->orX( $qb->expr()->isNull(self::A.'.endDate'), - $qb->expr()->lt(self::A.'.endDate', ':'.self::P) + $qb->expr()->gt(self::A.'.endDate', ':'.self::P) ) ) ) diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php new file mode 100644 index 000000000..f58246a25 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php @@ -0,0 +1,73 @@ +labelThirdPartyHelper->getLabel($key, $values, 'export.aggregator.course_work.by_handling_third_party.header'); + } + + public function getQueryKeys($data) + { + return [self::PREFIX.'_h3party']; + } + + public function getTitle() + { + return 'export.aggregator.course_work.by_handling_third_party.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + $qb + ->addSelect("IDENTITY(acpw.handlingThierParty) AS {$p}_h3party") + ->addGroupBy("{$p}_h3party"); + } + + public function applyOn() + { + return Declarations::SOCIAL_WORK_ACTION_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Enum/DateGroupingChoiceEnum.php b/src/Bundle/ChillPersonBundle/Export/Enum/DateGroupingChoiceEnum.php new file mode 100644 index 000000000..2b1ffc816 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Enum/DateGroupingChoiceEnum.php @@ -0,0 +1,19 @@ +add('handling_3parties', PickThirdpartyDynamicType::class, [ + 'label' => 'export.filter.work.by_handling3party.pick_3parties', + 'multiple' => true, + ]); + } + + public function getFormDefaultData(): array + { + return ['handling_3parties' => []]; + } + + public function describeAction($data, $format = 'string') + { + return [ + 'export.filter.work.by_handling3party.Only 3 parties %3parties%', + [ + '%3parties%' => implode( + ', ', + array_map(fn (ThirdParty $thirdParty) => $this->thirdPartyRender->renderString($thirdParty, []), $data['handling_3parties']) + ), + ], + ]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::PREFIX; + + $qb->andWhere("acpw.handlingThierParty IN (:{$p}_3ps)"); + $qb->setParameter("{$p}_3ps", $data['handling_3parties']); + } + + public function applyOn() + { + return Declarations::SOCIAL_WORK_ACTION_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php index bca812b86..66355e44d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php @@ -61,7 +61,7 @@ class StepFilterOnDate implements FilterInterface $qb->expr()->lte(self::A.'.startDate', ':'.self::P), $qb->expr()->orX( $qb->expr()->isNull(self::A.'.endDate'), - $qb->expr()->lt(self::A.'.endDate', ':'.self::P) + $qb->expr()->gt(self::A.'.endDate', ':'.self::P) ) ) ) diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php index 6430b5e99..8cd675abe 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php @@ -66,7 +66,7 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface { $builder ->add('date_calc', PickRollingDateType::class, [ - 'label' => 'Compute address at date', + 'label' => 'export.filter.person.by_address_ref_status.Address at date', 'required' => true, ]) ->add('ref_statuses', ChoiceType::class, [ diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php index 7e752395c..8f75d033c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/LabelPersonHelper.php @@ -18,6 +18,21 @@ class LabelPersonHelper { public function __construct(private readonly PersonRepository $personRepository, private readonly PersonRenderInterface $personRender) {} + public function getLabel(string $key, array $values, string $header): callable + { + return function (null|int|string $value) use ($header): string { + if ('_header' === $value) { + return $header; + } + + if ('' === $value || null === $value || null === $person = $this->personRepository->find($value)) { + return ''; + } + + return $this->personRender->renderString($person, []); + }; + } + public function getLabelMulti(string $key, array $values, string $header): callable { return function ($value) use ($header) { diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregatorTest.php new file mode 100644 index 000000000..ac7084de7 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregatorTest.php @@ -0,0 +1,66 @@ +get(ClosingDateAggregator::class); + self::$entityManager = self::$container->get(EntityManagerInterface::class); + } + + public function getAggregator() + { + return self::$closingDateAggregator; + } + + public function getFormData() + { + yield ['frequency' => 'YYYY']; + yield ['frequency' => 'YYYY-MM']; + yield ['frequency' => 'YYYY-IV']; + } + + public function getQueryBuilders() + { + self::bootKernel(); + self::$entityManager = self::$container->get(EntityManagerInterface::class); + + $data = [ + self::$entityManager->createQueryBuilder() + ->select('count(acp.id)') + ->from(AccompanyingPeriod::class, 'acp'), + ]; + + self::ensureKernelShutdown(); + + return $data; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregatorTest.php new file mode 100644 index 000000000..77d26b278 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregatorTest.php @@ -0,0 +1,66 @@ +get(OpeningDateAggregator::class); + self::$entityManager = self::$container->get(EntityManagerInterface::class); + } + + public function getAggregator() + { + return self::$openingDateAggregator; + } + + public function getFormData() + { + yield ['frequency' => 'YYYY']; + yield ['frequency' => 'YYYY-MM']; + yield ['frequency' => 'YYYY-IV']; + } + + public function getQueryBuilders() + { + self::bootKernel(); + self::$entityManager = self::$container->get(EntityManagerInterface::class); + + $data = [ + self::$entityManager->createQueryBuilder() + ->select('count(acp.id)') + ->from(AccompanyingPeriod::class, 'acp'), + ]; + + self::ensureKernelShutdown(); + + return $data; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregatorTest.php new file mode 100644 index 000000000..3677cedf2 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregatorTest.php @@ -0,0 +1,60 @@ +get(HandlingThirdPartyAggregator::class); + } + + public function getAggregator() + { + return self::$handlingThirdPartyAggregator; + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container + ->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(acpw.id)') + ->from(AccompanyingPeriodWork::class, 'acpw'), + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php new file mode 100644 index 000000000..e454a10fc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php @@ -0,0 +1,71 @@ +thirdPartyRender = self::$container->get(ThirdPartyRender::class); + } + + public function getFilter() + { + return new HandlingThirdPartyFilter($this->thirdPartyRender); + } + + public function getFormData() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + $thirdParties = $em->createQuery('SELECT tp FROM '.ThirdParty::class.' tp') + ->setMaxResults(2) + ->getResult(); + + return [ + [ + 'handling_3parties' => $thirdParties, + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('acpw.id') + ->from(AccompanyingPeriodWork::class, 'acpw'), + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml index c5a2dba68..4eaaf67b0 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml @@ -251,3 +251,11 @@ services: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ScopeWorkingOnCourseAggregator: tags: - { name: chill.export_aggregator, alias: accompanyingcourse_scope_working_on_course_aggregator } + + Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\OpeningDateAggregator: + tags: + - { name: chill.export_aggregator, alias: accompanyingcourse_opening_date_aggregator } + + Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ClosingDateAggregator: + tags: + - { name: chill.export_aggregator, alias: accompanyingcourse_closing_date_aggregator } diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml index c0e6abc4b..57be6bf40 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_social_actions.yaml @@ -1,116 +1,93 @@ services: + _defaults: + autowire: true + autoconfigure: true - ## Indicators - Chill\PersonBundle\Export\Export\CountAccompanyingPeriodWork: - autowire: true - autoconfigure: true - tags: - - { name: chill.export, alias: count_social_work_actions } + ## Indicators + Chill\PersonBundle\Export\Export\CountAccompanyingPeriodWork: + tags: + - { name: chill.export, alias: count_social_work_actions } - Chill\PersonBundle\Export\Export\ListAccompanyingPeriodWork: - autowire: true - autoconfigure: true - tags: - - { name: chill.export, alias: list_social_work_actions } + Chill\PersonBundle\Export\Export\ListAccompanyingPeriodWork: + tags: + - { name: chill.export, alias: list_social_work_actions } - ## FILTERS - chill.person.export.filter_social_work_type: - class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\SocialWorkTypeFilter - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_type_filter } + ## FILTERS + chill.person.export.filter_social_work_type: + class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\SocialWorkTypeFilter + tags: + - { name: chill.export_filter, alias: social_work_type_filter } - chill.person.export.filter_scope: - class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ScopeFilter - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_actions_scope_filter } + chill.person.export.filter_scope: + class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ScopeFilter + tags: + - { name: chill.export_filter, alias: social_work_actions_scope_filter } - chill.person.export.filter_job: - class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\JobFilter - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_actions_job_filter } + chill.person.export.filter_job: + class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\JobFilter + tags: + - { name: chill.export_filter, alias: social_work_actions_job_filter } - chill.person.export.filter_treatingagent: - class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ReferrerFilter - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_actions_treatingagent_filter } + chill.person.export.filter_treatingagent: + class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ReferrerFilter + tags: + - { name: chill.export_filter, alias: social_work_actions_treatingagent_filter } - Chill\PersonBundle\Export\Filter\SocialWorkFilters\CurrentActionFilter: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_actions_current_filter } + Chill\PersonBundle\Export\Filter\SocialWorkFilters\CurrentActionFilter: + tags: + - { name: chill.export_filter, alias: social_work_actions_current_filter } - Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkStartDateBetweenDateFilter: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_actions_start_btw_dates_filter } + Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkStartDateBetweenDateFilter: + tags: + - { name: chill.export_filter, alias: social_work_actions_start_btw_dates_filter } - Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkEndDateBetweenDateFilter: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: social_work_actions_end_btw_dates_filter } + Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkEndDateBetweenDateFilter: + tags: + - { name: chill.export_filter, alias: social_work_actions_end_btw_dates_filter } - ## AGGREGATORS - chill.person.export.aggregator_action_type: - class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ActionTypeAggregator - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_action_type_aggregator } + ## AGGREGATORS + chill.person.export.aggregator_action_type: + class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ActionTypeAggregator + tags: + - { name: chill.export_aggregator, alias: social_work_actions_action_type_aggregator } - chill.person.export.aggregator_treatingagent_scope: - class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ScopeAggregator - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_scope_aggregator } + chill.person.export.aggregator_treatingagent_scope: + class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ScopeAggregator + tags: + - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_scope_aggregator } - chill.person.export.aggregator_treatingagent_job: - class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\JobAggregator - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_job_aggregator } + chill.person.export.aggregator_treatingagent_job: + class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\JobAggregator + tags: + - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_job_aggregator } - Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ReferrerAggregator: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_aggregator } + Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ReferrerAggregator: + tags: + - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_aggregator } - chill.person.export.aggregator_goal: - class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalAggregator - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_goal_aggregator } + chill.person.export.aggregator_goal: + class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalAggregator + tags: + - { name: chill.export_aggregator, alias: social_work_actions_goal_aggregator } - chill.person.export.aggregator_result: - class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ResultAggregator - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_result_aggregator } + chill.person.export.aggregator_result: + class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ResultAggregator + tags: + - { name: chill.export_aggregator, alias: social_work_actions_result_aggregator } - chill.person.export.aggregator_goalresult: - class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalResultAggregator - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_goal_result_aggregator } + chill.person.export.aggregator_goalresult: + class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalResultAggregator + tags: + - { name: chill.export_aggregator, alias: social_work_actions_goal_result_aggregator } - Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\CurrentActionAggregator: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: social_work_actions_current_aggregator } + Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\CurrentActionAggregator: + tags: + - { name: chill.export_aggregator, alias: social_work_actions_current_aggregator } + + Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\HandlingThirdPartyAggregator: + tags: + - { name: chill.export_aggregator, alias: accompanyingcourse_handling3party_aggregator } + + Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\HandlingThirdPartyFilter: + tags: + - { name: chill.export_filter, alias: 'acpw_handling3party_filter'} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 097b5e79f..66aecff1f 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -480,7 +480,7 @@ acp_geog_agg_unitrefid: Clé de la zone géographique Geographical layer: Couche géographique Select a geographical layer: Choisir une couche géographique Group people by geographical unit based on his address: Grouper les usagers par zone géographique (sur base de l'adresse) -Filter by person's geographical unit (based on address): Filter les usagers par zone géographique (sur base de l'adresse) +Filter by person's geographical unit (based on address): Filtrer les usagers par zone géographique (sur base de l'adresse) Filter by socialaction: Filtrer les parcours par action d'accompagnement Accepted socialactions: Actions d'accompagnement @@ -981,6 +981,11 @@ notification: personId: Identifiant de l'usager export: + enum: + frequency: + YYYY-IW: par semaine + YYYY-MM: par mois + YYYY: par année export: acp_stats: avg_duration: Moyenne de la durée de participation de chaque usager concerné @@ -1037,6 +1042,14 @@ export: Calc date: Date de calcul du service de l'intervenant by_scope: Group course by scope: Grouper les parcours par service + by_opening_date: + title: Grouper les parcours par date d'ouverture + frequency: Intervalle de regroupement + header: Date d'ouverture des parcours (période) + by_closing_date: + title: Grouper les parcours par date de cloture + frequency: Intervalle de regroupement + header: Date de cloture des parcours (période) course_work: by_treating_agent: @@ -1053,6 +1066,9 @@ export: by_agent_job: Group by treating agent job: Grouper les actions par métier de l'agent traitant Calc date: Date de calcul du métier de l'agent traitant + by_handling_third_party: + title: Grouper les actions par tiers traitant + header: Tiers traitant eval: by_end_date: @@ -1084,21 +1100,22 @@ export: Persons filtered by no composition at %date%: Uniquement les usagers sans composition de ménage à la date du %date% Date calc: Date de calcul by_address_ref_status: - Filter by person's address ref status: Filtrer par comparaison avec l'adresse de référence + Filter by person's address ref status: Filtrer les usagers par comparaison avec l'adresse de référence to_review: Diffère de l'adresse de référence reviewed: Diffère de l'adresse de référence mais conservé par l'utilisateur match: Identique à l'adresse de référence Filtered by person\'s address status computed at %datecalc%, only %statuses%: Filtré par comparaison à l'adresse de référence, calculé à %datecalc%, seulement %statuses% Status: Statut + Address at date: Adresse à la date course: having_info_within_interval: - title: Filter les parcours ayant reçu une intervention entre deux dates + title: Filtrer les parcours ayant reçu une intervention entre deux dates start_date: Début de la période end_date: Fin de la période Only course with events between %startDate% and %endDate%: Seulement les parcours ayant reçu une intervention entre le %startDate% et le %endDate% by_user_working: - title: Filter les parcours par intervenant, entre deux dates + title: Filtrer les parcours par intervenant, entre deux dates 'Filtered by user working on course: only %users%, between %start_date% and %end_date%': 'Filtré par intervenants sur le parcours: seulement %users%, entre le %start_date% et le %end_date%' User working after: Intervention après le User working before: Intervention avant le @@ -1168,6 +1185,10 @@ export: Calc date: Date à laquelle l'agent est en situation de désignation sur l'action calc_date_help: Il s'agit de la date à laquelle l'agent est actif comme agent traitant de l'action, et non la date à la quelle l'agent est désigné comme agent traitant. "Filtered by treating agent: only %agents%": "Filtré par agent traitant: uniquement %agents%" + by_handling3party: + title: Filtrer les actions par tiers traitant + Only 3 parties %3parties%: "Seulement les actions d'accompagnement qui ont pour tiers traitant: %3parties%" + pick_3parties: Tiers traitants des actions list: person_with_acp: diff --git a/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php b/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php index 9b4cbfa75..e92cdae93 100644 --- a/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php +++ b/src/Bundle/ChillThirdPartyBundle/Export/Helper/LabelThirdPartyHelper.php @@ -25,7 +25,7 @@ class LabelThirdPartyHelper return $header; } - if (null === $value || null === $thirdParty = $this->thirdPartyRepository->find($value)) { + if ('' === $value || null === $value || null === $thirdParty = $this->thirdPartyRepository->find($value)) { return ''; } diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index f62a5beff..2bb17cca4 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -128,8 +128,8 @@ export: thirdParties: Tiers intervenant # exports filters/aggregators -Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% +Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% is thirdparty: Le demandeur est un tiers -Filter by person's who have a residential address located at a thirdparty of type: Filtrer les personnes qui ont une addresse de résidence chez un tiers de catégorie "xxx" -"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%" +Filter by person's who have a residential address located at a thirdparty of type: Filtrer les usagers qui ont une addresse de résidence chez un tiers de catégorie "xxx" +"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%"