diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index bf73b4f81..efbb972c1 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -101,6 +101,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/exports_accompanying_course.yaml'); $loader->load('services/exports_social_actions.yaml'); $loader->load('services/exports_evaluation.yaml'); + $loader->load('services/exports_household.yaml'); } /** diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php new file mode 100644 index 000000000..d52c410e5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php @@ -0,0 +1,98 @@ +evaluationRepository = $evaluationRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + /** + * @inheritDoc + */ + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ($value === '_header') { + return 'Evaluation type'; + } + + $ev = $this->evaluationRepository->find($value); + + return $this->translatableStringHelper->localize($ev->getTitle()); + }; + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data): array + { + return ['evaluationtype_aggregator']; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Group by evaluation type'; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->join('eval.evaluation', 'ev'); + $qb->addSelect('ev.id AS evaluationtype_aggregator'); + + $groupBy = $qb->getDQLPart('groupBy'); + + if (!empty($groupBy)) { + $qb->addGroupBy('evaluationtype_aggregator'); + } else { + $qb->groupBy('evaluationtype_aggregator'); + } + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::EVAL_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php new file mode 100644 index 000000000..c629fe0bf --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php @@ -0,0 +1,108 @@ +translator = $translator; + } + + /** + * @inheritDoc + */ + public function getLabels($key, array $values, $data) + { + return function ($value): string { + + if ($value === '_header') { + return 'Number of children'; + } + + return $this->translator->trans( + 'household_composition.numberOfChildren children in household', [ + 'numberOfChildren' => $value + ]); + }; + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data): array + { + return ['childrennumber_aggregator']; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('on_date', ChillDateType::class, [ + 'data' => new \DateTime('now'), + ]); + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Group by number of children'; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->addSelect('composition.numberOfChildren AS childrennumber_aggregator'); + + $groupBy = $qb->getDQLPart('groupBy'); + + if (!empty($groupBy)) { + $qb->addGroupBy('childrennumber_aggregator'); + } else { + $qb->groupBy('childrennumber_aggregator'); + } + + // add date in where clause + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->andX( + $qb->expr()->lte('composition.startDate', ':ondate'), + $qb->expr()->orX( + $qb->expr()->gt('composition.endDate', ':ondate'), + $qb->expr()->isNull('composition.endDate') + ) + ); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::HOUSEHOLD_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php new file mode 100644 index 000000000..3e292ceec --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php @@ -0,0 +1,124 @@ +typeRepository = $typeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + /** + * @inheritDoc + */ + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ($value === '_header') { + return 'Composition'; + } + + $c = $this->typeRepository->find($value); + + return $this->translatableStringHelper->localize( + $c->getLabel() + ); + }; + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data): array + { + return ['composition_aggregator']; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('on_date', ChillDateType::class, [ + 'data' => new \DateTime('now'), + ]); + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Group by composition'; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $qb->addSelect('IDENTITY(composition.householdCompositionType) AS composition_aggregator'); + + $groupBy = $qb->getDQLPart('groupBy'); + + if (!empty($groupBy)) { + $qb->addGroupBy('composition_aggregator'); + } else { + $qb->groupBy('composition_aggregator'); + } + + // add date in where clause + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->andX( + $qb->expr()->lte('composition.startDate', ':ondate'), + $qb->expr()->orX( + $qb->expr()->gt('composition.endDate', ':ondate'), + $qb->expr()->isNull('composition.endDate') + ) + ); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('ondate', $data['on_date'], Types::DATE_MUTABLE); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::HOUSEHOLD_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Declarations.php b/src/Bundle/ChillPersonBundle/Export/Declarations.php index 5a88d988b..b8e2cd69c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Declarations.php +++ b/src/Bundle/ChillPersonBundle/Export/Declarations.php @@ -27,4 +27,6 @@ abstract class Declarations public const SOCIAL_WORK_ACTION_TYPE = 'social_actions'; public const EVAL_TYPE = 'evaluation'; + + public const HOUSEHOLD_TYPE = 'household'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php b/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php index 985a69df6..22bd07b0c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php @@ -5,7 +5,7 @@ namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; -use Chill\PersonBundle\Entity\SocialWork\Evaluation; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\EntityManagerInterface; @@ -22,7 +22,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface public function __construct( EntityManagerInterface $em ) { - $this->evaluationRepository = $em->getRepository(Evaluation::class); + $this->evaluationRepository = $em->getRepository(AccompanyingPeriodWorkEvaluation::class); } /** diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountHousehold.php b/src/Bundle/ChillPersonBundle/Export/Export/CountHousehold.php new file mode 100644 index 000000000..c9b2b541a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountHousehold.php @@ -0,0 +1,141 @@ +acpRepository = $em->getRepository(AccompanyingPeriod::class); + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + // TODO: Implement buildForm() method. + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Count households'; + } + + /** + * @inheritDoc + */ + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + /** + * @inheritDoc + */ + public function getDescription(): string + { + return 'Count household by various parameters.'; + } + + /** + * @inheritDoc + */ + public function getLabels($key, array $values, $data) + { + if ('export_result' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + $labels = array_combine($values, $values); + $labels['_header'] = $this->getTitle(); + + return static function ($value) use ($labels) { + return $labels[$value]; + }; + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data): array + { + return ['export_result']; + } + + /** + * @inheritDoc + */ + public function getResult($qb, $data) + { + return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + /** + * @inheritDoc + */ + public function getType(): string + { + return Declarations::HOUSEHOLD_TYPE; + } + + /** + * @inheritDoc + */ + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->acpRepository->createQueryBuilder('acp') + ->join('acp.participations', 'acppart') + ->join('acppart.person', 'person') + ->join('person.householdParticipations', 'householdmember') + ->join('householdmember.household', 'household') + ->join('household.compositions', 'composition') + ; + + $qb->select('COUNT(DISTINCT householdmember.household) AS export_result'); + + return $qb; + } + + /** + * @inheritDoc + */ + public function requiredRole() + { + // TODO HouseholdVoter::STATS !?? + return new Role(AccompanyingPeriodVoter::STATS); + } + + /** + * @inheritDoc + */ + public function supportsModifiers(): array + { + return [ + Declarations::HOUSEHOLD_TYPE, + ]; + } + + public function getGroup(): string + { + return 'Exports of households'; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php index b41347026..e6c56f11e 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; -use Chill\CustomFieldsBundle\Form\Type\ChoicesType; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\FilterInterface; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php new file mode 100644 index 000000000..6df5e38bf --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php @@ -0,0 +1,96 @@ +translatableStringHelper = $translatableStringHelper; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_evaluationtype', EntityType::class, [ + 'class' => Evaluation::class, + 'choice_label' => function (Evaluation $ev): string { + return $this->translatableStringHelper->localize($ev->getTitle()); + }, + 'multiple' => true, + 'expanded' => true + ]); + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Filter by evaluation type'; + } + + /** + * @inheritDoc + */ + public function describeAction($data, $format = 'string'): array + { + $evals = []; + + foreach ($data['accepted_evaluationtype'] as $ev) { + $evals[] = $this->translatableStringHelper->localize($ev->getTitle()); + } + + return ['Filtered by evaluation type: only %evals%', [ + '%evals%' => implode(", ou ", $evals) + ]]; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + $clause = $qb->expr()->in('eval.evaluation', ':evaluationtype'); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('evaluationtype', $data['accepted_evaluationtype']); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::EVAL_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php new file mode 100644 index 000000000..de84d5877 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php @@ -0,0 +1,103 @@ + true, + 'is not specified' => false, + ]; + + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('maxdate', ChoiceType::class, [ + 'choices' => self::MAXDATE_CHOICES, + 'multiple' => false, + 'expanded' => true + ]); + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Filter by maxdate'; + } + + /** + * @inheritDoc + */ + public function describeAction($data, $format = 'string'): array + { + foreach (self::MAXDATE_CHOICES as $k => $v) { + if ($v === $data['maxdate']) { + $choice = $k; + } + } + + return ['Filtered by maxdate: only %choice%', [ + '%choice%' => $this->translator->trans($choice) + ]]; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + + if ($data['maxdate'] === true) { + $clause = $qb->expr()->isNotNull('eval.maxDate'); + } else { + $clause = $qb->expr()->isNull('eval.maxDate'); + } + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + + dump($data['maxdate']); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::EVAL_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php new file mode 100644 index 000000000..f8a36e4b9 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php @@ -0,0 +1,119 @@ +translatableStringHelper = $translatableStringHelper; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('accepted_composition', EntityType::class, [ + 'class' => HouseholdCompositionType::class, + 'choice_label' => function (HouseholdCompositionType $type) { + return $this->translatableStringHelper->localize( + $type->getLabel() + ); + }, + 'multiple' => true, + 'expanded' => true, + ]) + ->add('on_date', ChillDateType::class, [ + 'data' => new \DateTime('now'), + ]) + ; + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Filter by composition'; + } + + /** + * @inheritDoc + */ + public function describeAction($data, $format = 'string'): array + { + $compositions = []; + + foreach ($data['accepted_composition'] as $c) { + $compositions[] = $this->translatableStringHelper->localize( + $c->getLabel() + ); + } + + return ['Filtered by composition: only %compositions% on %ondate%', [ + '%compositions%' => implode(", ou ", $compositions), + '%ondate%' => $data['on_date']->format('d-m-Y') + ]]; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + + $clause = $qb->expr()->andX( + $qb->expr()->in('composition.householdCompositionType', ':compositions'), + $qb->expr()->andX( + $qb->expr()->lte('composition.startDate', ':ondate'), + $qb->expr()->orX( + $qb->expr()->gt('composition.endDate', ':ondate'), + $qb->expr()->isNull('composition.endDate') + ) + ) + ); + + if ($where instanceof Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + $qb->setParameter('compositions', $data['accepted_composition']); + $qb->setParameter('ondate', $data['on_date'], Types::DATE_MUTABLE); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::HOUSEHOLD_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml index 03cd37ee3..e21e0b6c1 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_evaluation.yaml @@ -9,5 +9,25 @@ services: - { name: chill.export, alias: count_evaluation } ## Filters + chill.person.export.filter_evaluationtype: + class: Chill\PersonBundle\Export\Filter\EvaluationFilters\EvaluationTypeFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: accompanyingcourse_evaluationtype_filter } + + chill.person.export.filter_maxdate: + class: Chill\PersonBundle\Export\Filter\EvaluationFilters\MaxDateFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: accompanyingcourse_maxdate_filter } + ## Aggregators + chill.person.export.aggregator_evaluationtype: + class: Chill\PersonBundle\Export\Aggregator\EvaluationAggregators\EvaluationTypeAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: accompanyingcourse_evaluationtype_aggregator } \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml new file mode 100644 index 000000000..0b8c5f1ce --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/exports_household.yaml @@ -0,0 +1,32 @@ +services: + + ## Indicators + chill.person.export.count_household: + class: Chill\PersonBundle\Export\Export\CountHousehold + autowire: true + autoconfigure: true + tags: + - { name: chill.export, alias: count_household } + + ## Filters + chill.person.export.filter_household_composition: + class: Chill\PersonBundle\Export\Filter\HouseholdFilters\CompositionFilter + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: household_composition_filter } + + ## Aggregators + chill.person.export.aggregator_household_composition: + class: Chill\PersonBundle\Export\Aggregator\HouseholdAggregators\CompositionAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: household_composition_aggregator } + + chill.person.export.aggregator_household_childrennumber: + class: Chill\PersonBundle\Export\Aggregator\HouseholdAggregators\ChildrenNumberAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: household_childrennumber_aggregator } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 95756883d..da499c725 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -355,6 +355,10 @@ Exports of evaluations: Exports des évaluations Count evaluations: Nombre d'évaluations Count evaluation by various parameters.: Compte le nombre d'évaluations selon différents filtres. +Exports of households: Exports des ménages +Count households: Nombre de ménages +Count household by various parameters.: Compte le nombre de ménages selon différents filtres. + ## filters Filter by person gender: Filtrer par genre de la personne Accepted genders: Genres acceptés @@ -514,7 +518,6 @@ On date: Actifs à cette date Filtered by active at least one day between dates: Filtrer les parcours actifs au moins un jour dans la période "Filtered by actives courses: at least one day between %datefrom% and %dateto%": "Filtrer les parcours actifs: au moins un jour entre le %datefrom% et le %dateto%" - Filtered by referrers: Filtrer par référent Accepted referrers: Référents "Filtered by referrer: only %referrers%": "Filtré par référent: uniquement %referrers%" @@ -525,6 +528,25 @@ Date from: Date de début Date to: Date de fin "Filtered by opening dates: between %datefrom% and %dateto%": "Filtrer les parcours ouverts entre deux dates: entre le %datefrom% et le %dateto%" +Filter by evaluation type: Filtrer par type d'évaluation +Accepted evaluationtype: Évaluations +"Filtered by evaluation type: only %evals%": "Filtré par type d'évaluation: uniquement %evals%" +Group by evaluation type: Grouper par type d'évaluation +Evaluation type: Type d'évaluation + +Filter by maxdate: Filtrer par date d'échéance +Maxdate: '' +is specified: La date d'échéance est spécifiée +is not specified: La date d'échéance n'est pas spécifiée +"Filtered by maxdate: only %choice%": "Filtré par date d'échéance: uniquement si %choice%" + +Filter by composition: Filtrer par composition familiale +Accepted composition: Composition familiale +"Filtered by composition: only %compositions% on %ondate%": "Filtré par composition familiale: uniquement %compositions%, en date du %ondate%" +Group by composition: Grouper par composition familiale + +Group by number of children: Grouper par nombre d'enfants + ## aggregators Group people by nationality: Grouper les personnes par nationalités Group by level: Grouper par niveau