diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php index 6dda268e2..1c2f02d3b 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php @@ -34,31 +34,26 @@ class DateAggregator implements AggregatorInterface public function getLabels($key, array $values, $data) { return function ($value) use ($data): string { + if ($value === '_header') { + return 'by '. $data['frequency']; + } switch ($data['frequency']) { case 'month': - if ($value === '_header') { - return 'by month'; - } $month = \DateTime::createFromFormat('!m', $value); - return - sprintf("%02d", $value) .'/'. - $month->format('F') // TODO translation ?!? - ; + return sprintf( + "%02d (%s)", + $value, + $month->format('M') + ); case 'week': - if ($value === '_header') { - return 'by week'; - } - return $this->translator->trans('for week') .' '. $value ; + //return $this->translator->trans('for week') .' '. $value ; case 'year': - if ($value === '_header') { - return 'by year'; - } - return $this->translator->trans('in year') .' '. $value ; + //return $this->translator->trans('in year') .' '. $value ; default: - throw new RuntimeException(sprintf('The value %s is not valid', $value)); + return $value; } }; } @@ -91,34 +86,24 @@ class DateAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { + $order = null; + switch ($data['frequency']) { case 'month': - $qb - //->addSelect("TO_CHAR(activity.date,'Mon') AS MON") - ->addSelect('EXTRACT(month FROM activity.date) AS date_aggregator') - //->orderBy('date_aggregator') - ; - break; + $fmt = 'MM'; break; case 'week': - $qb - ->addSelect("TO_CHAR(activity.date, 'IW') AS date_aggregator") - //->orderBy('date_aggregator') - ; - break; + $fmt = 'IW'; break; case 'year': - $qb - //->addSelect("TO_CHAR(activity.date, 'YYYY') AS date_aggregator") - ->addSelect('EXTRACT(year FROM activity.date) AS date_aggregator') - //->orderBy('date_aggregator', 'ASC') - ; - break; + $fmt = 'YYYY'; $order = 'DESC'; break; default: throw new RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency'])); } + $qb->addSelect(sprintf("TO_CHAR(activity.date, '%s') AS date_aggregator", $fmt)); + $groupBy = $qb->getDQLPart('groupBy'); if (!empty($groupBy)) { @@ -126,6 +111,14 @@ class DateAggregator implements AggregatorInterface } else { $qb->groupBy('date_aggregator'); } + + $orderBy = $qb->getDQLPart('orderBy'); + + if (!empty($orderBy)) { + $qb->addOrderBy('date_aggregator', $order); + } else { + $qb->orderBy('date_aggregator', $order); + } } public function applyOn(): string diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php index 44d20724b..72ca2b461 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Extract.php @@ -2,7 +2,9 @@ namespace Chill\MainBundle\Doctrine\DQL; +use Doctrine\ORM\Query\AST\Functions\DateDiffFunction; use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; @@ -12,13 +14,17 @@ use Doctrine\ORM\Query\SqlWalker; * Extract postgresql function * https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT * - * Usage : EXTRACT(field FROM value) + * Usage : EXTRACT(field FROM timestamp) + * TODO allow interval usage -> EXTRACT(field FROM interval) */ class Extract extends FunctionNode { private string $field; - private PathExpression $value; + private $value; + //private PathExpression $value; + //private FunctionNode $value; + //private DateDiffFunction $value; public function getSql(SqlWalker $sqlWalker) { @@ -39,7 +45,8 @@ class Extract extends FunctionNode $parser->match(Lexer::T_FROM); - $this->value = $parser->ScalarExpression(); + //$this->value = $parser->ScalarExpression(); + $this->value = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php new file mode 100644 index 000000000..9e119e30d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php @@ -0,0 +1,72 @@ +id; + } + + public function getLayerName(): ?string + { + return $this->layerName; + } + + public function getUnitName(): ?string + { + return $this->unitName; + } + + public function setLayerName(?string $layerName): self + { + $this->layerName = $layerName; + + return $this; + } + + public function setUnitName(?string $unitName): self + { + $this->unitName = $unitName; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220829132409.php b/src/Bundle/ChillMainBundle/migrations/Version20220829132409.php new file mode 100644 index 000000000..0bb09ef74 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220829132409.php @@ -0,0 +1,31 @@ +addSql('CREATE SEQUENCE chill_main_geographical_unit_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_main_geographical_unit (id INT NOT NULL, geom TEXT DEFAULT NULL, layerName VARCHAR(255) DEFAULT NULL, unitName VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP SEQUENCE chill_main_geographical_unit_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_geographical_unit'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php new file mode 100644 index 000000000..2a0215e82 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php @@ -0,0 +1,148 @@ + 1 mois, + * - jusqu'à 45 jours => 1 mois, + * 15 | 45 | 75 + * --+----o----+----o----+---- + * | 30 | 60 | + * etc.) + */ +class DurationAggregator implements AggregatorInterface +{ + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + /** + * @inheritDoc + */ + public function getLabels($key, array $values, $data) + { + return function ($value) use ($data): string { + + if ($value === '_header') { + return $this->translator->trans('Rounded month duration'); + } + + if ($value === null) { + return $this->translator->trans('current duration'); // when closingDate is null + } + + if ($value === 0) { + return $this->translator->trans("duration 0 month"); + } + + return ''. $value . $this->translator->trans(' months'); + }; + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data): array + { + return ['duration_aggregator']; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + // no form + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Group by duration'; + } + + /** + * @inheritDoc + */ + public function addRole() + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + // OUI + ->addSelect(' + (acp.closingDate - acp.openingDate +15) *12/365 + AS duration_aggregator' + ) + //->addSelect('DATE_DIFF(acp.closingDate, acp.openingDate) AS duration_aggregator') + //->addSelect('EXTRACT(month FROM acp.openingDate) AS duration_aggregator') + //->addSelect("DATE_SUB(acp.openingDate, 6, 'day') AS duration_aggregator") + + // TODO adapter la fonction extract pour l'utiliser avec des intervals: extract(month from interval) + // et ajouter une fonction custom qui calcule les intervals, comme doctrineum/date-interval + // https://packagist.org/packages/doctrineum/date-interval#3.1.0 + // (composer fait un conflit de dépendance) + + //->addSelect(" + // EXTRACT( + // month FROM + // DATE_INTERVAL(acp.closingDate, acp.openingDate) + // ) + // AS duration_aggregator") + + // NON + //->addSelect("BETWEEN acp.openingDate AND acp.closingDate AS duration_aggregator") + //->addSelect("EXTRACT(month FROM DATE_SUB(acp.openingDate, 6, 'day')) AS duration_aggregator") + //->addSelect('EXTRACT(month FROM DATE_DIFF(acp.closingDate, acp.openingDate)) AS duration_aggregator') + /* + ->addSelect(' + ( CASE + WHEN EXTRACT(day FROM DATE_DIFF(acp.closingDate, acp.openingDate)) > 15 + THEN EXTRACT(month FROM DATE_DIFF(acp.closingDate, acp.openingDate)) +1 + ELSE EXTRACT(month FROM DATE_DIFF(acp.closingDate, acp.openingDate)) + END ) AS duration_aggregator + ') + */ + ; + + $groupBy = $qb->getDQLPart('groupBy'); + + if (!empty($groupBy)) { + $qb->addGroupBy('duration_aggregator'); + } else { + $qb->groupBy('duration_aggregator'); + } + + $qb->orderBy('duration_aggregator'); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::ACP_TYPE; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php index 8a18eb2fe..5445e20a4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php @@ -2,13 +2,14 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Entity\GeographicalUnit; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\PersonBundle\Export\Declarations; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; /** @@ -23,12 +24,6 @@ use Symfony\Component\Form\FormBuilderInterface; */ class GeographicalUnitStatFilter implements FilterInterface { - - private const LOCTYPE = [ - 'center' => 'center', - // TODO not yet implemented: https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/626 - ]; - /** * @inheritDoc */ @@ -38,9 +33,12 @@ class GeographicalUnitStatFilter implements FilterInterface ->add('date', ChillDateType::class, [ 'data' => new \DateTime(), ]) - ->add('accepted_loctype', ChoiceType::class, [ - 'choices' => self::LOCTYPE, - 'multiple' => false, + ->add('accepted_loctype', EntityType::class, [ + 'class' => GeographicalUnit::class, + 'choice_label' => function (GeographicalUnit $u) { + return $u->getUnitName(); + }, + 'multiple' => true, 'expanded' => true, ]) ; diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml index 155ad11df..2b578b4f9 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml @@ -241,3 +241,10 @@ services: tags: - { name: chill.export_aggregator, alias: accompanyingcourse_referrer_aggregator } + chill.person.export.aggregator_duration: + class: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\DurationAggregator + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: accompanyingcourse_duration_aggregator } + diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 9bb2c7e3f..e105dd45f 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -569,6 +569,11 @@ Group by composition: Grouper les ménages par composition familiale Group by number of children: Grouper les ménages par nombre d'enfants ## persons aggregators +Group by duration: Grouper par durée du parcours +Rounded month duration: Durée en mois (arrondie) +current duration: en cours +duration 0 month: 0 mois (<15 jours) +' months': ' mois' Group people by nationality: Grouper les personnes par nationalités Group by level: Grouper par niveau Group by continents: Grouper par continent