diff --git a/Export/Aggregator/ActivityTypeAggregator.php b/Export/Aggregator/ActivityTypeAggregator.php new file mode 100644 index 000000000..544ae98ac --- /dev/null +++ b/Export/Aggregator/ActivityTypeAggregator.php @@ -0,0 +1,157 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\Export\Aggregator; + +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Export\AggregatorInterface; +use Symfony\Component\Security\Core\Role\Role; +use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Doctrine\ORM\EntityRepository; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\ORM\Query\Expr\Join; + +/** + * + * + * @author Julien Fastré + */ +class ActivityTypeAggregator implements AggregatorInterface +{ + + /** + * + * @var EntityRepository + */ + protected $typeRepository; + + /** + * + * @var TranslatableStringHelper + */ + protected $stringHelper; + + const KEY = 'activity_type_aggregator'; + + public function __construct( + EntityRepository $typeRepository, + TranslatableStringHelper $stringHelper + ) { + $this->typeRepository = $typeRepository; + $this->stringHelper = $stringHelper; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + // add select element + $qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY)); + + // make a jointure only if needed + /*$join = $qb->getDQLPart('join'); + if ( + (array_key_exists('activity', $join) + && + !$this->checkJoinAlreadyDefined($join['activity'], 'reasons') + ) + OR + (! array_key_exists('activity', $join)) + ) { + $qb->add( + 'join', + array('activity' => + new Join(Join::INNER_JOIN, 'activity.reasons', 'reasons') + ), + true); + } + + // join category if necessary + if ($alias === 'activity_categories_id') { + // add join only if needed + if (!$this->checkJoinAlreadyDefined($qb->getDQLPart('join')['activity'], 'category')) { + $qb->join('reasons.category', 'category'); + } + }*/ + + // add the "group by" part + $groupBy = $qb->addGroupBy(self::KEY); + } + + /** + * Check if a join between Activity and another alias + * + * @param Join[] $joins + * @param string $alias the alias to search for + * @return boolean + */ + private function checkJoinAlreadyDefined(array $joins, $alias) + { + foreach ($joins as $join) { + if ($join->getAlias() === $alias) { + return true; + } + } + + return false; + } + + public function applyOn() + { + return 'activity'; + } + + public function buildForm(FormBuilderInterface $builder) + { + // no form required for this aggregator + } + + public function getTitle() + { + return "Aggregate by activity type"; + } + + public function addRole() + { + return new Role(ActivityStatsVoter::STATS); + } + + public function getLabels($key, array $values, $data) + { + // for performance reason, we load data from db only once + $this->typeRepository->findBy(array('id' => $values)); + + return function($value) use ($data) { + if ($value === '_header') { + return 'Activity type'; + } + + /* @var $r \Chill\ActivityBundle\Entity\ActivityType */ + $t = $this->typeRepository->find($value); + + return $this->stringHelper->localize($t->getName()); + }; + + } + + public function getQueryKeys($data) + { + return array(self::KEY); + } + +} diff --git a/Export/Export/StatActivityDuration.php b/Export/Export/StatActivityDuration.php new file mode 100644 index 000000000..8fd4ec00b --- /dev/null +++ b/Export/Export/StatActivityDuration.php @@ -0,0 +1,159 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\Export\Export; + +use Chill\MainBundle\Export\ExportInterface; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Security\Core\Role\Role; +use Doctrine\ORM\Query; +use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; +use Doctrine\ORM\EntityManagerInterface; + +/** + * This export allow to compute stats on activity duration. + * + * The desired stat must be given in constructor. + * + * + * @author Julien Fastré + */ +class StatActivityDuration implements ExportInterface +{ + /** + * + * @var EntityManagerInterface + */ + protected $entityManager; + + const SUM = 'sum'; + + /** + * The action for this report. + * + * @var string + */ + protected $action; + + /** + * constructor + * + * @param EntityManagerInterface $em + * @param string $action the stat to perform + */ + public function __construct( + EntityManagerInterface $em, + $action = 'sum' + ) + { + $this->entityManager = $em; + $this->action = $action; + } + + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + { + + } + + public function getDescription() + { + if ($this->action === self::SUM) { + return "Sum activities duration by various parameters."; + } + } + + public function getTitle() + { + if ($this->action === self::SUM) { + return "Sum activity duration"; + } + + } + + public function getType() + { + return 'activity'; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = array()) + { + $centers = array_map(function($el) { return $el['center']; }, $acl); + $qb = $this->entityManager->createQueryBuilder(); + + if ($this->action === self::SUM) { + $select = "SUM(activity.durationTime) AS export_stat_activity"; + } + + $qb->select($select) + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.person', 'person') + ->join('person.center', 'center') + ->where($qb->expr()->in('center', ':centers')) + ->setParameter(':centers', $centers) + ; + + return $qb; + } + + public function supportsModifiers() + { + return array('person', 'activity'); + } + + public function requiredRole() + { + return new Role(ActivityStatsVoter::STATS); + } + + public function getAllowedFormattersTypes() + { + return array(\Chill\MainBundle\Export\FormatterInterface::TYPE_TABULAR); + } + + public function getLabels($key, array $values, $data) + { + if ($key !== 'export_stat_activity') { + throw new \LogicException("the key $key is not used by this export"); + } + + switch ($this->action) { + case self::SUM: + $header = "Sum of activities duration"; + } + + return function($value) use ($header) { + return $value === '_header' ? + $header + : + $value + ; + }; + } + + public function getQueryKeys($data) + { + return array('export_stat_activity'); + } + + public function getResult($qb, $data) + { + return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + +} diff --git a/Resources/config/services/export.yml b/Resources/config/services/export.yml index daf4fef57..871d65e2b 100644 --- a/Resources/config/services/export.yml +++ b/Resources/config/services/export.yml @@ -6,6 +6,14 @@ services: tags: - { name: chill.export, alias: 'count_activity' } + chill.activity.export.sum_activity_duration: + class: Chill\ActivityBundle\Export\Export\StatActivityDuration + arguments: + - "@doctrine.orm.entity_manager" + - "sum" + tags: + - { name: chill.export, alias: 'sum_activity_duration' } + chill.activity.export.list_activity: class: Chill\ActivityBundle\Export\Export\ListActivity arguments: @@ -46,3 +54,11 @@ services: - "@chill.main.helper.translatable_string" tags: - { name: chill.export_aggregator, alias: activity_reason_aggregator } + + chill.activity.export.type_aggregator: + class: Chill\ActivityBundle\Export\Aggregator\ActivityTypeAggregator + arguments: + - "@chill_activity.repository.activity_type" + - "@chill.main.helper.translatable_string" + tags: + - { name: chill.export_aggregator, alias: activity_type_aggregator } diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index b426463dc..3af06d041 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -106,6 +106,9 @@ Filter by reason: Filtrer par sujet d'activité "Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'activité: uniquement entre %date_from% et %date_to%" +Filtered by person having an activity in a period: Uniquement les personnes ayant eu une activité dans la période donnée +Implied in an activity after this date: Impliqué dans une activité après cette date +Implied in an activity before this date: Impliqué dans une activité avant cette date Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par personnes associées à une activité entre %date_from% et %date_to% avec les sujets %reasons_name% Activity reasons for those activities: Sujets de ces activités diff --git a/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php b/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php new file mode 100644 index 000000000..d3e4efd84 --- /dev/null +++ b/Tests/Export/Aggregator/ActivityTypeAggregatorTest.php @@ -0,0 +1,92 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\ActivityBundle\Tests\Aggregator; + +use Chill\MainBundle\Test\Export\AbstractAggregatorTest; + +/** + * Add tests for ActivityTypeAggregator + * + * @author Julien Fastré + */ +class ActivityTypeAggregatorTest extends AbstractAggregatorTest +{ + /** + * + * @var \Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator + */ + private $aggregator; + + public function setUp() + { + static::bootKernel(); + + $container = static::$kernel->getContainer(); + + $this->aggregator = $container->get('chill.activity.export.type_aggregator'); + + // add a fake request with a default locale (used in translatable string) + $prophet = new \Prophecy\Prophet; + $request = $prophet->prophesize(); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $container->get('request_stack') + ->push($request->reveal()); + } + + public function getAggregator() + { + return $this->aggregator; + } + + public function getFormData() + { + return array( + array() + ); + } + + public function getQueryBuilders() + { + if (static::$kernel === null) { + static::bootKernel(); + } + + $em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + return array( + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from('ChillActivityBundle:Activity', 'activity'), + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.reasons', 'reasons'), + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.reasons', 'reasons') + ->join('reasons.category', 'category') + ); + } + +} diff --git a/Tests/Export/Export/StatActivityDurationSumTest.php b/Tests/Export/Export/StatActivityDurationSumTest.php new file mode 100644 index 000000000..f43374fd8 --- /dev/null +++ b/Tests/Export/Export/StatActivityDurationSumTest.php @@ -0,0 +1,50 @@ + + */ +class StatActivityDurationSumTest extends AbstractExportTest +{ + /** + * + * @var \Chill\ActivityBundle\Export\Export\StatActivityDuration + */ + private $export; + + public function setUp() + { + static::bootKernel(); + + /* @var $container \Symfony\Component\DependencyInjection\ContainerInterface */ + $container = self::$kernel->getContainer(); + + $this->export = $container->get('chill.activity.export.sum_activity_duration'); + } + + public function getExport() + { + return $this->export; + } + + public function getFormData() + { + return array( + array() + ); + } + + public function getModifiersCombination() + { + return array( + array('activity'), + array('activity', 'person') + ); + } + +} diff --git a/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php b/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php new file mode 100644 index 000000000..5b922e6f3 --- /dev/null +++ b/Tests/Export/Filter/PersonHavingActivityBetweenDateFilterTest.php @@ -0,0 +1,117 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\ActivityBundle\Tests\Filter; + +use Chill\MainBundle\Test\Export\AbstractFilterTest; + +/** + * + * + * @author Julien Fastré + */ +class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest +{ + /** + * + * @var \Chill\PersonBundle\Export\Filter\PersonHavingActivityBetweenDateFilter + */ + private $filter; + + public function setUp() + { + static::bootKernel(); + + $container = static::$kernel->getContainer(); + + $this->filter = $container->get('chill.activity.export.' + . 'person_having_an_activity_between_date_filter'); + + // add a fake request with a default locale (used in translatable string) + $prophet = new \Prophecy\Prophet; + $request = $prophet->prophesize(); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $container->get('request_stack') + ->push($request->reveal()); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData() + { + $date_from = \DateTime::createFromFormat('Y-m-d', '2015-01-15'); + $date_to = new \DateTime(); // today + $reasons = $this->getActivityReasons(); + + + $data = array(); + for ($i = 0; $i < 4; $i++) { + $data[] = array( + 'date_from' => $date_from, + 'date_to' => $date_to, + 'reasons' => array_slice($reasons, 0, 1 + $i) + ); + } + + return $data; + } + + /** + * Return all activity reasons + * + * @return \Chill\ActivityBundle\Entity\ActivityReason[] + */ + private function getActivityReasons() + { + if (static::$kernel === null) { + static::bootKernel(); + } + + return static::$kernel->getContainer() + ->get('chill_activity.repository.reason') + ->findAll(); + } + + public function getQueryBuilders() + { + if (static::$kernel === null) { + static::bootKernel(); + } + + $em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + return array( + $em->createQueryBuilder() + ->select('count(person.id)') + ->from('ChillPersonBundle:Person', 'person') + // add a fake where clause + ->where('person.id > 0'), + $em->createQueryBuilder() + ->select('count(person.id)') + ->from('ChillActivityBundle:Activity', 'activity') + ->join('activity.person', 'person') + // add a fake where clause + ->where('person.id > 0'), + ); + } +}