From c3ef8d112c33285f825f6f433d514dad376c8119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 14 May 2021 16:25:56 +0200 Subject: [PATCH 01/81] first impl for global timeline: apply on activities --- .../ChillActivityBundle/Entity/Activity.php | 2 +- .../Repository/ActivityACLAwareRepository.php | 246 ++++++++++++++++++ .../Repository/ActivityRepository.php | 42 +++ .../activity_person_context.html.twig | 8 +- .../Timeline/TimelineActivityProvider.php | 85 ++++-- .../ChillActivityBundle/config/services.yaml | 2 + .../config/services/repositories.yaml | 18 +- .../Controller/TimelineCenterController.php | 91 +++++++ .../views/Timeline/chain_timelines.html.twig | 7 + .../Resources/views/Timeline/index.html.twig | 22 +- .../Timeline/TimelineBuilder.php | 88 ++++--- src/Bundle/ChillMainBundle/config/routes.yaml | 4 + .../config/services/timeline.yaml | 5 +- .../Controller/TimelinePersonController.php | 29 +-- .../Resources/views/Entity/person.html.twig | 18 ++ .../Templating/Entity/PersonRender.php | 33 +-- .../config/services/controller.yaml | 1 + .../config/services/templating.yaml | 1 + 18 files changed, 604 insertions(+), 98 deletions(-) create mode 100644 src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php create mode 100644 src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php create mode 100644 src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Timeline/chain_timelines.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig diff --git a/src/Bundle/ChillActivityBundle/Entity/Activity.php b/src/Bundle/ChillActivityBundle/Entity/Activity.php index e96db03db..afa631761 100644 --- a/src/Bundle/ChillActivityBundle/Entity/Activity.php +++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php @@ -38,7 +38,7 @@ use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency; * Class Activity * * @package Chill\ActivityBundle\Entity - * @ORM\Entity() + * @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository") * @ORM\Table(name="activity") * @ORM\HasLifecycleCallbacks() * @UserCircleConsistency( diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php new file mode 100644 index 000000000..b8684a0a5 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -0,0 +1,246 @@ +, + * + * 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\Repository; + +use Chill\ActivityBundle\Entity\Activity; +use Chill\PersonBundle\Entity\Person; +use Chill\ActivityBundle\Repository\ActivityRepository; +use Chill\ActivityBundle\Security\Authorization\ActivityVoter; +use Chill\MainBundle\Entity\Scope; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query\Expr\Orx; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Role\Role; +use Doctrine\ORM\EntityManagerInterface; + + +final class ActivityACLAwareRepository +{ + private AuthorizationHelper $authorizationHelper; + + private TokenStorageInterface $tokenStorage; + + private ActivityRepository $repository; + + private EntityManagerInterface $em; + + public function __construct( + AuthorizationHelper $authorizationHelper, + TokenStorageInterface $tokenStorage, + ActivityRepository $repository, + EntityManagerInterface $em + ) { + $this->authorizationHelper = $authorizationHelper; + $this->tokenStorage = $tokenStorage; + $this->repository = $repository; + $this->em = $em; + } + + public function queryTimelineIndexer(string $context, array $args = []): array + { + $metadataActivity = $this->em->getClassMetadata(Activity::class); + + $from = $this->getFromClauseCenter($args); + $where = $this->getWhereClause($context, $args); + + return [ + 'id' => $metadataActivity->getTableName() + .'.'.$metadataActivity->getColumnName('id'), + 'type' => 'activity', + 'date' => $metadataActivity->getTableName() + .'.'.$metadataActivity->getColumnName('date'), + 'FROM' => $from, + 'WHERE' => $where + ]; + } + + private function getFromClauseCenter(array $args): string + { + $metadataActivity = $this->em->getClassMetadata(Activity::class); + $metadataPerson = $this->em->getClassMetadata(Person::class); + $associationMapping = $metadataActivity->getAssociationMapping('person'); + + return $metadataActivity->getTableName().' JOIN ' + .$metadataPerson->getTableName().' ON ' + .$metadataPerson->getTableName().'.'. + $associationMapping['joinColumns'][0]['referencedColumnName'] + .' = ' + .$associationMapping['joinColumns'][0]['name'] + ; + } + + private function getWhereClause(string $context, array $args): array + { + $where = ''; + $parameters = []; + + // condition will be: + // FROM activity JOIN person -- not set by us + // ON activity.person_id = person.id -- not set by us + // WHERE -- not set by us + // activity.person_id = ? AND -- only if $context = person + // ( -- begin loop through centers, center#0 + // person.center_id = ? + // AND ( -- begin loop for scopes within centers + // activity.scope_id = ? -- scope#0 + // OR -- if scope#i where i > 0 + // activity.scope_id = ? -- scope#1 + // ) + // ) + // OR -- if center#i where i > 0 + // ( -- begin loop through centers, center#1 + // person.center_id = ? + // AND ( -- begin loop for scopes within centers + // activity.scope_id = ? -- scope#0 + // OR -- if scope#i where i > 0 + // activity.scope_id = ? -- scope#1 + // ) + // ) + + $metadataActivity = $this->em->getClassMetadata(Activity::class); + $metadataPerson = $this->em->getClassMetadata(Person::class); + $activityToPerson = $metadataActivity->getAssociationMapping('person')['joinColumns'][0]['name']; + $activityToScope = $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name']; + $personToCenter = $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name']; + + + // acls: + $role = new Role(ActivityVoter::SEE); + $reachableCenters = $this->authorizationHelper->getReachableCenters($this->tokenStorage->getToken()->getUser(), + $role); + + if (count($reachableCenters) === 0) { + // insert a dummy condition + return 'FALSE = TRUE'; + } + + if ($context === 'person') { + // we start with activities having the person_id linked to person + $where .= sprintf('%s = ? AND ', $activityToPerson); + $parameters[] = $person->getId(); + } + + // we add acl (reachable center and scopes) + $where .= '('; // first loop for the for centers + $centersI = 0; // like centers#i + foreach ($reachableCenters as $center) { + // we pass if not in centers + if (!\in_array($center, $args['centers'])) { + continue; + } + // we get all the reachable scopes for this center + $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), $role, $center); + // we get the ids for those scopes + $reachablesScopesId = array_map( + function(Scope $scope) { return $scope->getId(); }, + $reachableScopes + ); + + // if not the first center + if ($centersI > 0) { + $where .= ') OR ('; + } + + // condition for the center + $where .= sprintf(' %s.%s = ? ', $metadataPerson->getTableName(), $personToCenter); + $parameters[] = $center->getId(); + + // begin loop for scopes + $where .= ' AND ('; + $scopesI = 0; //like scope#i + + foreach ($reachablesScopesId as $scopeId) { + if ($scopesI > 0) { + $where .= ' OR '; + } + $where .= sprintf(' %s.%s = ? ', $metadataActivity->getTableName(), $activityToScope); + $parameters[] = $scopeId; + $scopesI ++; + } + // close loop for scopes + $where .= ') '; + $centersI++; + } + // close loop for centers + $where .= ')'; + + return [$where, $parameters]; + } + +} +/* + $qb = $this->repository->createQueryBuilder('a'); + $qb->select(['a.id', "'activity'", 'a.date']); + $qb->join('a.person', 'p'); + + switch($context) { + case 'center': + $qb->where($this->queryTimelineIndexerWhereForCenter($qb, $args['centers'])); + break; + default: + throw new \LogicException('context not supported'); + } + + if ($from) { + $qb->andWhere($qb->gt('a.date', ':from')); + $qb->setParameter('from', $from); + } + + if ($to) { + $qb->andWhere($qb->gt('a.date', ':to')); + $qb->setParameter('to', $to); + } + + return $qb->getQuery(); + } + + private function queryTimelineIndexerWhereForCenter(QueryBuilder $qb, array $centers): Orx + { + $i = 0; + $orx = $qb->expr()->orX(); + + foreach ($centers as $center) { + $andx = $qb->expr()->andX(); + $andx->add($qb->expr()->eq('p.center', ":center_$i")); + $qb->setParameter("center_$i", $center); + $i++; + + $scopes = $this->authorizationHelper->getReachableCircles( + $this->tokenStorage->getToken()->getUser(), + new Role(ActivityVoter::SEE_DETAILS), + $center, + ); + + foreach ($scopes as $scope) { + $andx->add($qb->expr()->eq('a.scope', ":scope_$i")); + $qb->setParameter("scope_$i", $scope); + $i++; + } + + $orx->add($andx); + } + + return $orx; + } +} */ diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php new file mode 100644 index 000000000..b5155d4c9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -0,0 +1,42 @@ +, + * + * 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\Repository; + +use Chill\ActivityBundle\Entity\Activity; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @method AccompanyingPeriodParticipation|null find($id, $lockMode = null, $lockVersion = null) + * @method AccompanyingPeriodParticipation|null findOneBy(array $criteria, array $orderBy = null) + * @method AccompanyingPeriodParticipation[] findAll() + * @method AccompanyingPeriodParticipation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ActivityRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Activity::class); + } + +} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig index dddd05cf7..ce9a68ea4 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Timeline/activity_person_context.html.twig @@ -1,11 +1,11 @@ {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
-

{{ activity.date|format_date('long') }} / {{ 'Activity'|trans }}

+

{% if 'person' != context %}{{ activity.person|chill_entity_render_box({'addLink': true}) }} / {% endif %}{{ activity.date|format_date('long') }} / {{ 'Activity'|trans }}

{{ '%user% has done an %activity_type%'|trans( { - '%user%' : user, + '%user%' : activity.user, '%activity_type%': activity.type.name|localize_translatable_string, '%date%' : activity.date|format_date('long') } ) }} @@ -29,13 +29,13 @@
  • - + {{ 'Show the activity'|trans }}
  • {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
  • - + {{ 'Edit the activity'|trans }}
  • diff --git a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php index c87a17af4..52e1027a8 100644 --- a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php +++ b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php @@ -21,6 +21,7 @@ namespace Chill\ActivityBundle\Timeline; use Chill\MainBundle\Timeline\TimelineProviderInterface; +use Chill\ActivityBundle\Repository\ActivityACLAwareRepository; use Doctrine\ORM\EntityManager; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -55,6 +56,10 @@ class TimelineActivityProvider implements TimelineProviderInterface * @var \Chill\MainBundle\Entity\User */ protected $user; + + protected ActivityACLAwareRepository $aclAwareRepository; + + private const SUPPORTED_CONTEXTS = [ 'center', 'person']; /** * TimelineActivityProvider constructor. @@ -66,11 +71,13 @@ class TimelineActivityProvider implements TimelineProviderInterface public function __construct( EntityManager $em, AuthorizationHelper $helper, - TokenStorageInterface $storage + TokenStorageInterface $storage, + ActivityACLAwareRepository $aclAwareRepository ) { $this->em = $em; $this->helper = $helper; + $this->aclAwareRepository = $aclAwareRepository; if (!$storage->getToken()->getUser() instanceof \Chill\MainBundle\Entity\User) { @@ -86,10 +93,13 @@ class TimelineActivityProvider implements TimelineProviderInterface */ public function fetchQuery($context, array $args) { - $this->checkContext($context); + //$this->checkContext($context); + // + if ('center' === $context) { + return $this->aclAwareRepository->queryTimelineIndexer($context, $args); + } $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); - $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); return array( 'id' => $metadataActivity->getTableName() @@ -102,10 +112,40 @@ class TimelineActivityProvider implements TimelineProviderInterface $args['person']) ); } - - private function getWhereClause(ClassMetadata $metadataActivity, - ClassMetadata $metadataPerson, Person $person) + + private function getFromClause(string $context) { + switch ($context) { + case 'person': + return $this->getFromClausePerson($metadataActivity, $metadataPerson); + } + } + + private function getWhereClause(string $context, array $args) + { + switch ($context) { + case 'person': + return $this->getWhereClause($args['person']); + } + } + + /** + * + * @var $centers array|Center[] + */ + private function getWhereClauseForCenter(array $centers) + { + $clause = ""; + $role = new Role('CHILL_ACTIVITY_SEE'); + + + } + + private function getWhereClauseForPerson(Person $person) + { + $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); + $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); + $role = new Role('CHILL_ACTIVITY_SEE'); $reachableCenters = $this->helper->getReachableCenters($this->user, $role); @@ -144,9 +184,25 @@ class TimelineActivityProvider implements TimelineProviderInterface return $whereClause; } - private function getFromClause(ClassMetadata $metadataActivity, - ClassMetadata $metadataPerson) + private function getFromClausePerson() { + $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); + $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); + $associationMapping = $metadataActivity->getAssociationMapping('person'); + + return $metadataActivity->getTableName().' JOIN ' + .$metadataPerson->getTableName().' ON ' + .$metadataPerson->getTableName().'.'. + $associationMapping['joinColumns'][0]['referencedColumnName'] + .' = ' + .$associationMapping['joinColumns'][0]['name'] + ; + } + + private function getFromClauseCenter() + { + $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); + $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); $associationMapping = $metadataActivity->getAssociationMapping('person'); return $metadataActivity->getTableName().' JOIN ' @@ -183,14 +239,13 @@ class TimelineActivityProvider implements TimelineProviderInterface { $this->checkContext($context); - return array( + return [ 'template' => 'ChillActivityBundle:Timeline:activity_person_context.html.twig', - 'template_data' => array( + 'template_data' => [ 'activity' => $entity, - 'person' => $args['person'], - 'user' => $entity->getUser() - ) - ); + 'context' => $context + ] + ]; } /** @@ -210,7 +265,7 @@ class TimelineActivityProvider implements TimelineProviderInterface */ private function checkContext($context) { - if ($context !== 'person') { + if (FALSE === \in_array($context, self::SUPPORTED_CONTEXTS)) { throw new \LogicException("The context '$context' is not " . "supported. Currently only 'person' is supported"); } diff --git a/src/Bundle/ChillActivityBundle/config/services.yaml b/src/Bundle/ChillActivityBundle/config/services.yaml index 9ef22f43f..b4b1f8274 100644 --- a/src/Bundle/ChillActivityBundle/config/services.yaml +++ b/src/Bundle/ChillActivityBundle/config/services.yaml @@ -22,6 +22,8 @@ services: - '@doctrine.orm.entity_manager' - '@chill.main.security.authorization.helper' - '@security.token_storage' + - '@Chill\ActivityBundle\Repository\ActivityACLAwareRepository' public: true tags: - { name: chill.timeline, context: 'person' } + - { name: chill.timeline, context: 'center' } diff --git a/src/Bundle/ChillActivityBundle/config/services/repositories.yaml b/src/Bundle/ChillActivityBundle/config/services/repositories.yaml index 2867782b4..2f0a9b83c 100644 --- a/src/Bundle/ChillActivityBundle/config/services/repositories.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/repositories.yaml @@ -1,18 +1,32 @@ +--- services: chill_activity.repository.activity_type: class: Doctrine\ORM\EntityRepository factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - 'Chill\ActivityBundle\Entity\ActivityType' - + chill_activity.repository.reason: class: Doctrine\ORM\EntityRepository factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - 'Chill\ActivityBundle\Entity\ActivityReason' - + chill_activity.repository.reason_category: class: Doctrine\ORM\EntityRepository factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - 'Chill\ActivityBundle\Entity\ActivityReasonCategory' + + Chill\ActivityBundle\Repository\ActivityRepository: + tags: [doctrine.repository_service] + arguments: + - '@Doctrine\Persistence\ManagerRegistry' + + Chill\ActivityBundle\Repository\ActivityACLAwareRepository: + arguments: + $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' + $repository: '@Chill\ActivityBundle\Repository\ActivityRepository' + $em: '@Doctrine\ORM\EntityManagerInterface' + diff --git a/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php b/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php new file mode 100644 index 000000000..af17993ed --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/TimelineCenterController.php @@ -0,0 +1,91 @@ + + * + * 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\MainBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Chill\MainBundle\Timeline\TimelineBuilder; +use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Core\Security; + +class TimelineCenterController extends AbstractController +{ + + protected TimelineBuilder $timelineBuilder; + + protected PaginatorFactory $paginatorFactory; + + private Security $security; + + public function __construct( + TimelineBuilder $timelineBuilder, + PaginatorFactory $paginatorFactory, + Security $security + ) { + $this->timelineBuilder = $timelineBuilder; + $this->paginatorFactory = $paginatorFactory; + $this->security = $security; + } + + /** + * @Route("/{_locale}/center/timeline", + * name="chill_center_timeline", + * methods={"GET"} + * ) + */ + public function centerAction(Request $request) + { + // collect reachable center for each group + $user = $this->security->getUser(); + $centers = []; + foreach ($user->getGroupCenters() as $group) { + $centers[] = $group->getCenter(); + } + + if (0 === count($centers)) { + throw $this->createNotFoundException(); + } + + $nbItems = $this->timelineBuilder->countItems('center', + [ 'centers' => $centers ] + ); + + $paginator = $this->paginatorFactory->create($nbItems); + + return $this->render('@ChillMain/Timeline/index.html.twig', array + ( + 'timeline' => $this->timelineBuilder->getTimelineHTML( + 'center', + [ 'centers' => $centers ], + $paginator->getCurrentPage()->getFirstItemNumber(), + $paginator->getItemsPerPage() + ), + 'nb_items' => $nbItems, + 'paginator' => $paginator + ) + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Timeline/chain_timelines.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Timeline/chain_timelines.html.twig new file mode 100644 index 000000000..89dda9f8c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Timeline/chain_timelines.html.twig @@ -0,0 +1,7 @@ +
    + {% for result in results %} +
    + {% include result.template with result.template_data %} +
    + {% endfor %} +
    diff --git a/src/Bundle/ChillMainBundle/Resources/views/Timeline/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Timeline/index.html.twig index 89dda9f8c..d3a5f0780 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Timeline/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Timeline/index.html.twig @@ -1,7 +1,15 @@ -
    - {% for result in results %} -
    - {% include result.template with result.template_data %} -
    - {% endfor %} -
    +{% extends "@ChillMain/layout.html.twig" %} + +{% block content %} +
    +
    +

    {{ 'Global timeline'|trans }}

    + + {{ timeline|raw }} + + {% if nb_items > paginator.getItemsPerPage %} + {{ chill_pagination(paginator) }} + {% endif %} +
    +
    + {% endblock content %} diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php b/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php index 6d061d0a2..2e4102af6 100644 --- a/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php @@ -23,6 +23,8 @@ use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Doctrine\ORM\Query; +use Doctrine\ORM\NativeQuery; /** * Build timeline @@ -78,14 +80,14 @@ class TimelineBuilder implements ContainerAwareInterface */ public function getTimelineHTML($context, array $args, $firstItem = 0, $number = 20) { - $union = $this->buildUnionQuery($context, $args); + list($union, $parameters) = $this->buildUnionQuery($context, $args); //add ORDER BY clause and LIMIT $query = $union . sprintf(' ORDER BY date DESC LIMIT %d OFFSET %d', $number, $firstItem); // run query and handle results - $fetched = $this->runUnionQuery($query); + $fetched = $this->runUnionQuery($query, $parameters); $entitiesByKey = $this->getEntities($fetched, $context); return $this->render($fetched, $entitiesByKey, $context, $args); @@ -100,16 +102,18 @@ class TimelineBuilder implements ContainerAwareInterface */ public function countItems($context, array $args) { - $union = $this->buildUnionQuery($context, $args); - - // embed the union query inside a count query - $count = sprintf('SELECT COUNT(sq.id) AS total FROM (%s) as sq', $union); - $rsm = (new ResultSetMapping()) ->addScalarResult('total', 'total', Type::INTEGER); + + list($select, $parameters) = $this->buildUnionQuery($context, $args); + + // embed the union query inside a count query + $countQuery = sprintf('SELECT COUNT(sq.id) AS total FROM (%s) as sq', $select); + + $nq = $this->em->createNativeQuery($countQuery, $rsm); + $nq->setParameters($parameters); - return $this->em->createNativeQuery($count, $rsm) - ->getSingleScalarResult(); + return $nq->getSingleScalarResult(); } /** @@ -154,40 +158,56 @@ class TimelineBuilder implements ContainerAwareInterface * * @uses self::buildSelectQuery to build individual SELECT queries * - * @param string $context - * @param mixed $args - * @param int $page - * @param int $number - * @return string * @throws \LogicException if no builder have been defined for this context + * @return array, where first element is the query, the second one an array with the parameters */ - private function buildUnionQuery($context, array $args) + private function buildUnionQuery(string $context, array $args): array { //append SELECT queries with UNION keyword between them $union = ''; + $parameters = []; + foreach($this->getProvidersByContext($context) as $provider) { - $select = $this->buildSelectQuery($provider, $context, $args); - $append = ($union === '') ? $select : ' UNION '.$select; + $data = $provider->fetchQuery($context, $args); + list($select, $selectParameters) = $this->buildSelectQuery($data); + $append = empty($union) ? $select : ' UNION '.$select; $union .= $append; + $parameters = array_merge($parameters, $selectParameters); } - return $union; + return [$union, $parameters]; } + + /** + * Hack to replace the arbitrary "AS" statement in DQL + * into proper SQL query + * TODO remove + private function replaceASInDQL(string $dql): string + { + $pattern = '/^(SELECT\s+[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s{0,},\s{0,}[a-zA-Z0-9\_\.\']{1,}\s+)(AS [a-z0-9\_]{1,})(\s+FROM.*)/'; + $replacements = '${1} AS id ${3} AS type ${5} AS date ${7}'; + + $s = \preg_replace($pattern, $replacements, $dql, 1); + + if (NULL === $s) { + throw new \RuntimeException('Could not replace the "AS" statement produced by '. + 'DQL with normal SQL AS: '.$dql); + } + + return $s; + } + */ /** * return the SQL SELECT query as a string, * - * @uses TimelineProfiderInterface::fetchQuery use the fetchQuery function - * @param \Chill\MainBundle\Timeline\TimelineProviderInterface $provider - * @param string $context - * @param mixed[] $args * @return string */ - private function buildSelectQuery(TimelineProviderInterface $provider, $context, array $args) + private function buildSelectQuery(array $data): array { - $data = $provider->fetchQuery($context, $args); - - return sprintf( + $parameters = []; + + $sql = sprintf( 'SELECT %s AS id, ' . '%s AS "date", ' . "'%s' AS type " @@ -197,16 +217,19 @@ class TimelineBuilder implements ContainerAwareInterface $data['date'], $data['type'], $data['FROM'], - $data['WHERE']); + is_string($data['WHERE']) ? $data['WHERE'] : $data['WHERE'][0] + ); + + return [$sql, $data['WHERE'][1]]; + } /** * run the UNION query and return result as an array * - * @param string $query - * @return array + * @return array an array with the results */ - private function runUnionQuery($query) + private function runUnionQuery(string $query, array $parameters): array { $resultSetMapping = (new ResultSetMapping()) ->addScalarResult('id', 'id') @@ -214,7 +237,8 @@ class TimelineBuilder implements ContainerAwareInterface ->addScalarResult('date', 'date'); return $this->em->createNativeQuery($query, $resultSetMapping) - ->getArrayResult(); + ->setParameters($parameters) + ->getArrayResult(); } /** @@ -274,7 +298,7 @@ class TimelineBuilder implements ContainerAwareInterface } return $this->container->get('templating') - ->render('@ChillMain/Timeline/index.html.twig', array( + ->render('@ChillMain/Timeline/chain_timelines.html.twig', array( 'results' => $timelineEntries )); diff --git a/src/Bundle/ChillMainBundle/config/routes.yaml b/src/Bundle/ChillMainBundle/config/routes.yaml index 3fd7eafab..33b4e67de 100644 --- a/src/Bundle/ChillMainBundle/config/routes.yaml +++ b/src/Bundle/ChillMainBundle/config/routes.yaml @@ -1,3 +1,7 @@ +chill_main_controllers: + resource: '../Controller/' + type: annotation + chill_main_admin_permissionsgroup: resource: "@ChillMainBundle/config/routes/permissionsgroup.yaml" prefix: "{_locale}/admin/permissionsgroup" diff --git a/src/Bundle/ChillMainBundle/config/services/timeline.yaml b/src/Bundle/ChillMainBundle/config/services/timeline.yaml index 241f6a6f1..fe830c7ab 100644 --- a/src/Bundle/ChillMainBundle/config/services/timeline.yaml +++ b/src/Bundle/ChillMainBundle/config/services/timeline.yaml @@ -4,4 +4,7 @@ services: arguments: - "@doctrine.orm.entity_manager" calls: - - [ setContainer, ["@service_container"]] \ No newline at end of file + - [ setContainer, ["@service_container"]] + # alias: + Chill\MainBundle\Timeline\TimelineBuilder: '@chill_main.timeline_builder' + diff --git a/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php b/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php index 774351581..4e3d67755 100644 --- a/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/TimelinePersonController.php @@ -27,32 +27,17 @@ use Symfony\Component\HttpFoundation\Request; use Chill\MainBundle\Timeline\TimelineBuilder; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Role\Role; -/** - * Class TimelinePersonController - * - * @package Chill\PersonBundle\Controller - * @author Julien Fastré - */ class TimelinePersonController extends AbstractController { - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; + protected EventDispatcherInterface $eventDispatcher; - /** - * - * @var TimelineBuilder - */ - protected $timelineBuilder; + protected TimelineBuilder $timelineBuilder; - /** - * - * @var PaginatorFactory - */ - protected $paginatorFactory; + protected PaginatorFactory $paginatorFactory; /** * TimelinePersonController constructor. @@ -62,11 +47,13 @@ class TimelinePersonController extends AbstractController public function __construct( EventDispatcherInterface $eventDispatcher, TimelineBuilder $timelineBuilder, - PaginatorFactory $paginatorFactory + PaginatorFactory $paginatorFactory, + AuthorizationHelper $authorizationHelper ) { $this->eventDispatcher = $eventDispatcher; $this->timelineBuilder = $timelineBuilder; $this->paginatorFactory = $paginatorFactory; + $this->authorizationHelper = $authorizationHelper; } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig new file mode 100644 index 000000000..54482ee29 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig @@ -0,0 +1,18 @@ + + {% if addLink and is_granted('CHILL_PERSON_SEE', person) %} + {% set showLink = true %} + + {% endif %} + {{ person.firstName }} + {{ person.lastName }} + {% if addAltNames %} + {% for n in person.altNames %} + {% if loop.first %}({% else %} {% endif %} + + {{ n.label }} + + {% if loop.last %}){% endif %} + {% endfor %} + {% endif %} + {% if showLink is defined %}{% endif %} + diff --git a/src/Bundle/ChillPersonBundle/Templating/Entity/PersonRender.php b/src/Bundle/ChillPersonBundle/Templating/Entity/PersonRender.php index d1f554492..717ee4fc6 100644 --- a/src/Bundle/ChillPersonBundle/Templating/Entity/PersonRender.php +++ b/src/Bundle/ChillPersonBundle/Templating/Entity/PersonRender.php @@ -23,6 +23,8 @@ namespace Chill\PersonBundle\Templating\Entity; use Chill\MainBundle\Templating\Entity\AbstractChillEntityRender; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; +use Symfony\Component\Templating\EngineInterface; + /** * Render a Person @@ -30,15 +32,16 @@ use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; */ class PersonRender extends AbstractChillEntityRender { - /** - * - * @var ConfigPersonAltNamesHelper - */ - protected $configAltNamesHelper; + private ConfigPersonAltNamesHelper $configAltNamesHelper; + + private EngineInterface $engine; - public function __construct(ConfigPersonAltNamesHelper $configAltNamesHelper) - { + public function __construct( + ConfigPersonAltNamesHelper $configAltNamesHelper, + EngineInterface $engine + ) { $this->configAltNamesHelper = $configAltNamesHelper; + $this->engine = $engine; } /** @@ -49,13 +52,13 @@ class PersonRender extends AbstractChillEntityRender */ public function renderBox($person, array $options): string { - return - $this->getDefaultOpeningBox('person'). - ''.$person->getFirstName().''. - ' '.$person->getLastName().''. - $this->addAltNames($person, true). - $this->getDefaultClosingBox() - ; + return $this->engine->render('@ChillPerson/Entity/person.html.twig', + [ + 'person' => $person, + 'addAltNames' => $this->configAltNamesHelper->hasAltNames(), + 'addLink' => $options['addLink'] ?? false + ] + ); } /** @@ -69,7 +72,7 @@ class PersonRender extends AbstractChillEntityRender return $person->getFirstName().' '.$person->getLastName() .$this->addAltNames($person, false); } - + protected function addAltNames(Person $person, bool $addSpan) { $str = ''; diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml index dc575f320..d36519765 100644 --- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml @@ -16,6 +16,7 @@ services: $eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface' $timelineBuilder: '@chill_main.timeline_builder' $paginatorFactory: '@chill_main.paginator_factory' + $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' tags: ['controller.service_arguments'] Chill\PersonBundle\Controller\AccompanyingPeriodController: diff --git a/src/Bundle/ChillPersonBundle/config/services/templating.yaml b/src/Bundle/ChillPersonBundle/config/services/templating.yaml index 3456c96de..338248540 100644 --- a/src/Bundle/ChillPersonBundle/config/services/templating.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/templating.yaml @@ -2,6 +2,7 @@ services: Chill\PersonBundle\Templating\Entity\PersonRender: arguments: $configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' + $engine: '@Symfony\Component\Templating\EngineInterface' tags: - 'chill.render_entity' From 0a9b8ba0b047ee120b644d399e2cace2144a593e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 17 May 2021 10:05:03 +0200 Subject: [PATCH 02/81] remove test that concerns old code --- phpunit.xml.dist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ab9e69052..3622f5c47 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,7 +20,12 @@ src/Bundle/ChillPersonBundle/Tests/ + src/Bundle/ChillPersonBundle/Tests/Export/* + + src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingPeriodControllerTest.php + + src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php From 8841af8d2b64a6affca947c5255e481273f084ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 17 May 2021 10:07:23 +0200 Subject: [PATCH 03/81] remove references to no-static container --- .../ChillPersonBundle/Tests/Form/Type/PickPersonTypeTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Tests/Form/Type/PickPersonTypeTest.php b/src/Bundle/ChillPersonBundle/Tests/Form/Type/PickPersonTypeTest.php index cb796b742..e26d007c4 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Form/Type/PickPersonTypeTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Form/Type/PickPersonTypeTest.php @@ -26,7 +26,6 @@ use Chill\PersonBundle\Form\Type\PickPersonType; /** * * - * @author Julien Fastré */ class PickPersonTypeTest extends KernelTestCase { @@ -86,7 +85,7 @@ class PickPersonTypeTest extends KernelTestCase */ public function testWithOptionCenter() { - $center = $this->container->get('doctrine.orm.entity_manager') + $center = self::$container->get('doctrine.orm.entity_manager') ->getRepository('ChillMainBundle:Center') ->findOneBy(array('name' => 'Center A')) ; @@ -117,7 +116,7 @@ class PickPersonTypeTest extends KernelTestCase */ public function testWithOptionCenters() { - $centers = $this->container->get('doctrine.orm.entity_manager') + $centers = self::$container->get('doctrine.orm.entity_manager') ->getRepository('ChillMainBundle:Center') ->findAll() ; From 73653744d7c84ef5d6a4005529a6f9ac284865d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 17 May 2021 13:23:58 +0200 Subject: [PATCH 04/81] Prepare for deprecation of class Role, and add method to filter centers --- .../Authorization/AuthorizationHelper.php | 60 ++++++++++++++----- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php index ed2dd499d..697158bf8 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php @@ -110,8 +110,6 @@ class AuthorizationHelper return false; } - $role = ($attribute instanceof Role) ? $attribute : new Role($attribute); - foreach ($user->getGroupCenters() as $groupCenter){ //filter on center if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) { @@ -119,8 +117,7 @@ class AuthorizationHelper //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role allow to reach the required role - if ($this->isRoleReached($role, - new Role($roleScope->getRole()))){ + if ($this->isRoleReached($attribute, $roleScope->getRole())) { //if yes, we have a right on something... // perform check on scope if necessary if ($entity instanceof HasScopeInterface) { @@ -149,12 +146,15 @@ class AuthorizationHelper * and optionnaly Scope * * @param User $user - * @param Role $role + * @param string|Role $role * @param null|Scope $scope * @return Center[] */ - public function getReachableCenters(User $user, Role $role, Scope $scope = null) + public function getReachableCenters(User $user, $role, Scope $scope = null) { + if ($role instanceof Role) { + $role = $role->getRole(); + } $centers = array(); foreach ($user->getGroupCenters() as $groupCenter){ @@ -162,8 +162,7 @@ class AuthorizationHelper //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role is in the reachable roles - if ($this->isRoleReached($role, - new Role($roleScope->getRole()))) { + if ($this->isRoleReached($role, $roleScope->getRole())) { if ($scope === null) { $centers[] = $groupCenter->getCenter(); break 1; @@ -180,6 +179,30 @@ class AuthorizationHelper return $centers; } + + /** + * Filter an array of centers, return only center which are reachable + * + * @param User $user The user + * @param array $centers a list of centers which are going to be filtered + * @param string|Center $role + */ + public function filterReachableCenters(User $user, array $centers, $role): array + { + $results = []; + + if ($role instanceof Role) { + $role = $role->getRole(); + } + + foreach ($centers as $center) { + if ($this->userCanReachCenter($user, $center, $role)) { + $results[] = $center; + } + } + + return $results; + } /** * Return all reachable scope for a given user, center and role @@ -191,8 +214,12 @@ class AuthorizationHelper * @param Center $center * @return Scope[] */ - public function getReachableScopes(User $user, Role $role, Center $center) + public function getReachableScopes(User $user, $role, Center $center) { + if ($role instanceof Role) { + $role = $role->getRole(); + } + return $this->getReachableCircles($user, $role, $center); } @@ -200,12 +227,15 @@ class AuthorizationHelper * Return all reachable circle for a given user, center and role * * @param User $user - * @param Role $role + * @param string|Role $role * @param Center $center * @return Scope[] */ - public function getReachableCircles(User $user, Role $role, Center $center) + public function getReachableCircles(User $user, $role, Center $center) { + if ($role instanceof Role) { + $role = $role->getRole(); + } $scopes = array(); foreach ($user->getGroupCenters() as $groupCenter){ @@ -215,9 +245,7 @@ class AuthorizationHelper //iterate on roleScopes foreach($permissionGroup->getRoleScopes() as $roleScope) { //check that the role is in the reachable roles - if ($this->isRoleReached($role, - new Role($roleScope->getRole()))) { - + if ($this->isRoleReached($role, $roleScope->getRole())) { $scopes[] = $roleScope->getScope(); } } @@ -269,10 +297,10 @@ class AuthorizationHelper * @param Role $parentRole The role which should give access to $childRole * @return boolean true if the child role is granted by parent role */ - protected function isRoleReached(Role $childRole, Role $parentRole) + protected function isRoleReached($childRole, $parentRole) { $reachableRoles = $this->roleHierarchy - ->getReachableRoles([$parentRole]); + ->getReachableRoleNames([$parentRole]); return in_array($childRole, $reachableRoles); } From ea477a98420cdb00990244a6fce7f3f3e27a0d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 17 May 2021 13:24:50 +0200 Subject: [PATCH 05/81] add accompanying period opening/closing to global timeline --- .../translations/messages.fr.yml | 8 ++ .../Entity/AccompanyingPeriod.php | 8 +- .../Resources/views/Entity/person.html.twig | 23 ++--- .../views/Timeline/closing_period.html.twig | 28 ++++-- .../views/Timeline/opening_period.html.twig | 28 ++++-- .../AbstractTimelineAccompanyingPeriod.php | 91 +++++++++++++++---- .../TimelineAccompanyingPeriodClosing.php | 20 ++-- .../TimelineAccompanyingPeriodOpening.php | 5 +- .../ChillPersonBundle/config/services.yaml | 6 ++ .../translations/messages.fr.yml | 5 +- 10 files changed, 155 insertions(+), 67 deletions(-) diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index a7b71a072..ecc595987 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -46,6 +46,11 @@ Back to the list: Retour à la liste #interval Years: Années +# misc date +Since %date%: Depuis le %date% +since %date%: depuis le %date% +Until %date%: Jusqu'au %date% +until %date%: jusqu'au %date% #elements used in software centers: centres Centers: Centres @@ -78,6 +83,9 @@ Results %start%-%end% of %total%: Résultats %start%-%end% sur %total% See all results: Voir tous les résultats Advanced search: Recherche avancée +# timeline +Global timeline: Historique global + #admin Create: Créer show: voir diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 18ee535b8..d7420bf57 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -302,13 +302,9 @@ class AccompanyingPeriod return false; } - public function setRemark(string $remark): self + public function setRemark(string $remark = null): self { - if ($remark === null) { - $remark = ''; - } - - $this->remark = $remark; + $this->remark = (string) $remark; return $this; } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig index 54482ee29..afcd2799c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig @@ -1,18 +1,15 @@ - {% if addLink and is_granted('CHILL_PERSON_SEE', person) %} - {% set showLink = true %} - - {% endif %} - {{ person.firstName }} + {%- if addLink and is_granted('CHILL_PERSON_SEE', person) -%} + {%- set showLink = true -%}{%- endif -%} + {{ person.firstName }} {{ person.lastName }} - {% if addAltNames %} - {% for n in person.altNames %} - {% if loop.first %}({% else %} {% endif %} + {%- if addAltNames -%} + {%- for n in person.altNames -%} + {%- if loop.first -%}({% else %} {%- endif -%} {{ n.label }} - {% if loop.last %}){% endif %} - {% endfor %} - {% endif %} - {% if showLink is defined %}{% endif %} - + {%- if loop.last %}) {% endif -%} + {%- endfor -%} + {%- endif -%} + {%- if showLink is defined -%}{%- endif -%} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Timeline/closing_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Timeline/closing_period.html.twig index 27f13fc96..76224636a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Timeline/closing_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Timeline/closing_period.html.twig @@ -1,11 +1,19 @@ -

    +
    +

    {{ period.closingDate|format_date('long') }} - / - - {{ 'Closing the accompanying period' | trans }} - - - - - -

    + / + {{ 'An accompanying period ends'|trans }} +

    + +
    +
    +
    {{ 'Participants'|trans }} :
    +
    +
      + {% for p in period.participations %} +
    • {{ p.person|chill_entity_render_box({ 'addLink': true }) }}: {{ 'since %date%'|trans({'%date%': p.startDate|format_date("long") } ) }}, {{ 'until %date%'|trans({'%date%': (p.endDate is not null ? p.endDate : period.closingDate)|format_date("long") }) }}
    • + {% endfor %} +
    +
    +
    +
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Timeline/opening_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Timeline/opening_period.html.twig index e6fa5752c..6987aee2b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Timeline/opening_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Timeline/opening_period.html.twig @@ -1,11 +1,19 @@ -

+
+

{{ period.openingDate|format_date('long') }} - / - - {{ 'Opening the accompanying period' | trans }} - - - - - -

+ / + {{ 'An accompanying period starts'|trans }} +

+ +
+
+
{{ 'Participants'|trans }} :
+
+
    + {% for p in period.participations %} +
  • {{ 'Since %date%'|trans( {'%date%': p.startDate|format_date("long") } ) }} : {{ p.person|chill_entity_render_box({ 'addLink': true }) }}
  • + {% endfor %} +
+
+
+
diff --git a/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php index 37f185bb2..6b5b0cfd8 100644 --- a/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php @@ -21,6 +21,13 @@ namespace Chill\PersonBundle\Timeline; use Chill\MainBundle\Timeline\TimelineProviderInterface; use Doctrine\ORM\EntityManager; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; +use Chill\MainBundle\Entity\Center; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Symfony\Component\Security\Core\Security; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; /** * Provide method to build timeline for accompanying periods @@ -28,19 +35,22 @@ use Doctrine\ORM\EntityManager; * This class is resued by TimelineAccompanyingPeriodOpening (for opening) * and TimelineAccompanyingPeriodClosing (for closing) * - * @author Julien Fastré */ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInterface { - /** - * - * @var EntityManager - */ - protected $em; + protected EntityManager $em; + + private Security $security; + + private AuthorizationHelper $authorizationHelper; + + private const SUPPORTED_CONTEXTS = [ 'person', 'center' ]; - public function __construct(EntityManager $em) + public function __construct(EntityManager $em, Security $security, AuthorizationHelper $authorizationHelper) { $this->em = $em; + $this->security = $security; + $this->authorizationHelper = $authorizationHelper; } /** @@ -72,25 +82,74 @@ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInt */ protected function basicFetchQuery($context, array $args) { - if ($context !== 'person') { + if (FALSE === \in_array($context, self::SUPPORTED_CONTEXTS)) { throw new \LogicException('TimelineAccompanyingPeriod is not able ' . 'to render context '.$context); } $metadata = $this->em - ->getClassMetadata('ChillPersonBundle:AccompanyingPeriod') + ->getClassMetadata(AccompanyingPeriodParticipation::class) ; return array( - 'id' => $metadata->getColumnName('id'), - 'FROM' => $metadata->getTableName(), - 'WHERE' => sprintf('%s = %d', - $metadata - ->getAssociationMapping('person')['joinColumns'][0]['name'], - $args['person']->getId()) + 'id' => "{$metadata->getTableName()}.{$metadata->getColumnName('id')}", + 'FROM' => $this->buildFromClause($context), + 'WHERE' => $this->buildWhereClause($context, $args) ); } + private function buildFromClause($context) + { + $period = $this->em->getClassMetadata(AccompanyingPeriod::class); + $participation = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class); + $person = $this->em->getClassMetadata(Person::class); + $join = $participation->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]; + $joinPerson = $participation->getAssociationMapping('person')['joinColumns'][0]; + + if ($context === 'person') { + return "{$period->getTableName()} ". + "JOIN {$participation->getTableName()} ". + "ON {$participation->getTableName()}.{$join['name']} = ". + "{$period->getTableName()}.{$join['referencedColumnName']}"; + } else { + return "{$period->getTableName()} ". + "JOIN {$participation->getTableName()} ". + "ON {$participation->getTableName()}.{$join['name']} = ". + "{$period->getTableName()}.{$join['referencedColumnName']} ". + "JOIN {$person->getTableName()} ". + "ON {$participation->getTableName()}.{$joinPerson['name']} = ". + "{$person->getTableName()}.{$joinPerson['referencedColumnName']}" + ; + } + + } + + protected function buildWhereClause($context, array $args): array + { + $participation = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class); + $join = $participation->getAssociationMapping('person')['joinColumns'][0]; + $person = $this->em->getClassMetadata(Person::class); + $joinCenter = $person->getAssociationMapping('center')['joinColumns'][0]; + $allowedCenters = $this->authorizationHelper->filterReachableCenters($this->security->getUser(), $args['centers'], PersonVoter::SEE); + + if ($context === 'center') { + $params = []; + $questionMarks = []; + $query = "{$person->getTableName()}.{$joinCenter['name']} IN ("; + foreach ($allowedCenters as $c) { + $questionMarks[] = '?'; + $params[] = $c->getId(); + } + $query .= \implode(", ", $questionMarks).")"; + + return [$query, $params]; + } elseif ($context === 'person') { + return [ "{$participation->getTableName()}.{$join['name']} = ?", [ $args['person']->getId() ]]; + } + + throw new \LogicException("this context is not supported: $context"); + } + /** * return the expected response for TimelineProviderInterface::getEntityTemplate * @@ -104,7 +163,7 @@ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInt { return array( 'template' => $template, - 'template_data' => ['person' => $args['person'], 'period' => $entity] + 'template_data' => ['period' => $entity, 'context' => $context] ); } } diff --git a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php index 05aebbfd4..2e3d953fa 100644 --- a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php +++ b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php @@ -21,6 +21,7 @@ namespace Chill\PersonBundle\Timeline; use Chill\MainBundle\Timeline\TimelineProviderInterface; use Doctrine\ORM\EntityManager; +use Chill\PersonBundle\Entity\AccompanyingPeriod; /** * Provide information for opening periods to timeline @@ -46,22 +47,27 @@ class TimelineAccompanyingPeriodClosing extends AbstractTimelineAccompanyingPeri public function fetchQuery($context, array $args) { $metadata = $this->em - ->getClassMetadata('ChillPersonBundle:AccompanyingPeriod'); + ->getClassMetadata(AccompanyingPeriod::class); $data = $this->basicFetchQuery($context, $args); $data['type'] = 'accompanying_period_closing'; $data['date'] = $metadata->getColumnName('closingDate'); - $data['WHERE'] = sprintf('%s = %d AND %s IS NOT NULL', - $metadata - ->getAssociationMapping('person')['joinColumns'][0]['name'], - $args['person']->getId(), - $metadata->getColumnName('closingDate')) - ; + $data['WHERE'] = $this->buildWhereClause($context, $args); return $data; } + protected function buildWhereClause($context, array $args): array + { + list($query, $params) = parent::buildWhereClause($context, $args); + $period = $this->em->getClassMetadata(AccompanyingPeriod::class); + + $query .= " AND {$period->getColumnName('closingDate')} IS NOT NULL "; + + return [ $query, $params ]; + } + /** * * {@inheritDoc} diff --git a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php index f8789b088..5d3222789 100644 --- a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php +++ b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php @@ -21,11 +21,10 @@ namespace Chill\PersonBundle\Timeline; use Chill\MainBundle\Timeline\TimelineProviderInterface; use Doctrine\ORM\EntityManager; +use Chill\PersonBundle\Entity\AccompanyingPeriod; /** * Provide information for opening periods to timeline - * - * @author Julien Fastré */ class TimelineAccompanyingPeriodOpening extends AbstractTimelineAccompanyingPeriod { @@ -46,7 +45,7 @@ class TimelineAccompanyingPeriodOpening extends AbstractTimelineAccompanyingPeri public function fetchQuery($context, array $args) { $metadata = $this->em - ->getClassMetadata('ChillPersonBundle:AccompanyingPeriod'); + ->getClassMetadata(AccompanyingPeriod::class); $data = $this->basicFetchQuery($context, $args); diff --git a/src/Bundle/ChillPersonBundle/config/services.yaml b/src/Bundle/ChillPersonBundle/config/services.yaml index bccb69b0d..3d257dc7b 100644 --- a/src/Bundle/ChillPersonBundle/config/services.yaml +++ b/src/Bundle/ChillPersonBundle/config/services.yaml @@ -16,17 +16,23 @@ services: class: Chill\PersonBundle\Timeline\TimelineAccompanyingPeriodOpening arguments: - "@doctrine.orm.entity_manager" + - '@Symfony\Component\Security\Core\Security' + - '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' public: true tags: - { name: chill.timeline, context: 'person' } + - { name: chill.timeline, context: 'center' } chill.person.timeline.accompanying_period_closing: class: Chill\PersonBundle\Timeline\TimelineAccompanyingPeriodClosing arguments: - "@doctrine.orm.entity_manager" + - '@Symfony\Component\Security\Core\Security' + - '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' public: true tags: - { name: chill.timeline, context: 'person' } + - { name: chill.timeline, context: 'center' } chill.person.security.authorization.person: class: Chill\PersonBundle\Security\Authorization\PersonVoter diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 1fe6e3956..0918f8864 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -150,6 +150,8 @@ Update accompanying period: Mettre à jour une période d'accompagnement 'Closing motive': 'Motif de clôture' 'Person details': 'Détails de la personne' 'Update details for %name%': 'Modifier détails de %name%' +An accompanying period ends: Une periode d'accompagnement se clôture +An accompanying period starts: Une période d'accompagnement est ouverte Any accompanying periods are open: Aucune période d'accompagnement ouverte An accompanying period is open: Une période d'accompagnement est ouverte Accompanying period list: Périodes d'accompagnement @@ -162,11 +164,10 @@ Pediod closing form is not valid: Le formulaire n'est pas valide Accompanying user: Accompagnant No accompanying user: Aucun accompagnant No data given: Pas d'information +Participants: Personnes impliquées # pickAPersonType Pick a person: Choisir une personne -#address -Since %date%: Depuis le %date% No address given: Pas d'adresse renseignée The address has been successfully updated: L'adresse a été mise à jour avec succès Update address for %name%: Mettre à jour une adresse pour %name% From f35889339de08bbf7c4ecf098797d2e4b865a7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 18 May 2021 19:20:50 +0200 Subject: [PATCH 06/81] remove comment in constructor --- .../Serializer/Normalizer/SocialIssueNormalizer.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php index e78febcde..383110764 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php @@ -14,14 +14,10 @@ class SocialIssueNormalizer implements NormalizerInterface, NormalizerAwareInter use NormalizerAwareTrait; - /** - * @param SocialIssueRender $render - */ public function __construct(SocialIssueRender $render) { $this->render = $render; } - public function normalize($socialIssue, string $format = null, array $context = []) { From 9d34968b8803a08c0a90a3db9f2bb8f4b7a04a36 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 14 May 2021 09:58:56 +0200 Subject: [PATCH 07/81] Fix `::getParticipationsContainsPerson`. 1. `::getParticipations()` does not accept any argument. 2. The filter predicate must return a boolean. --- .../ChillPersonBundle/Entity/AccompanyingPeriod.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 8ec017c07..1d5f00c1f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -348,12 +348,13 @@ class AccompanyingPeriod */ public function getParticipationsContainsPerson(Person $person): Collection { - return $this->getParticipations($person)->filter( - function(AccompanyingPeriodParticipation $participation) use ($person) { - if ($person === $participation->getPerson()) { - return $participation; + return $this + ->getParticipations() + ->filter( + static function(AccompanyingPeriodParticipation $participation) use ($person): bool { + return $person === $participation->getPerson(); } - }); + ); } /** From a6e0b16032e2399456b975cec5f05eceb2218ef0 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 14 May 2021 10:27:07 +0200 Subject: [PATCH 08/81] Fix `::getOpenParticipationContainsPerson` 1. The filter predicate must return a boolean 2. The $person variable is not needed --- .../ChillPersonBundle/Entity/AccompanyingPeriod.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 1d5f00c1f..f2161c78a 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -364,12 +364,13 @@ class AccompanyingPeriod */ public function getOpenParticipationContainsPerson(Person $person): ?AccompanyingPeriodParticipation { - $collection = $this->getParticipationsContainsPerson($person)->filter( - function(AccompanyingPeriodParticipation $participation) use ($person) { - if (NULL === $participation->getEndDate()) { - return $participation; + $collection = $this + ->getParticipationsContainsPerson($person) + ->filter( + static function(AccompanyingPeriodParticipation $participation): bool { + return null === $participation->getEndDate(); } - }); + ); return $collection->count() > 0 ? $collection->first() : NULL; } From 484259c8abfd18b5ea86c1527241931e4d565b4f Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 14 May 2021 10:43:21 +0200 Subject: [PATCH 09/81] Fix `::canBeReOpened`. 1. Fix call to `::getOpenParticipationContainsPerson` instead of `::getParticipationsContainsPerson`. 2. Use early returns to reduce cyclomatic complexity. 3. Avoid storing variable that are used only once. --- .../ChillPersonBundle/Entity/AccompanyingPeriod.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index f2161c78a..567e678e1 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -435,15 +435,16 @@ class AccompanyingPeriod return false; } - $participation = $this->getParticipationsContainsPerson($person); - if (!null === $participation) + $participation = $this->getOpenParticipationContainsPerson($person); + + if (null === $participation) { - $person = $participation->getPerson(); - $periods = $person->getAccompanyingPeriodsOrdered(); - return end($periods) === $this; + return false; } - return false; + $periods = $participation->getPerson()->getAccompanyingPeriodsOrdered(); + + return end($periods) === $this; } /** From 7595d70ada4bafd4463d01bb4abc9fc91151b172 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Fri, 14 May 2021 10:52:31 +0200 Subject: [PATCH 10/81] Fix `::getPersons`. 1. Add more typing informations. --- .../Entity/AccompanyingPeriod.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 567e678e1..0f78ee2bb 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -653,13 +653,17 @@ class AccompanyingPeriod /** * Get a list of all persons which are participating to this course + * + * @psalm-return Collection */ public function getPersons(): Collection { - return $this->participations->map( - function(AccompanyingPeriodParticipation $participation) { - return $participation->getPerson(); - } - ); + return $this + ->participations + ->map( + static function(AccompanyingPeriodParticipation $participation): Person { + return $participation->getPerson(); + } + ); } } From 74541f360bbc13b4843f5b7f03c8c2d7a47753e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 24 May 2021 20:26:33 +0200 Subject: [PATCH 11/81] fix activity timeline using TimelineSingleQuery --- .../Repository/ActivityACLAwareRepository.php | 5 +- .../Timeline/TimelineActivityProvider.php | 125 ++++++-------- .../Timeline/TimelineBuilder.php | 7 +- .../Timeline/TimelineSingleQuery.php | 155 ++++++++++++++++++ 4 files changed, 210 insertions(+), 82 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index b8684a0a5..ae2c448e6 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -62,7 +62,7 @@ final class ActivityACLAwareRepository $metadataActivity = $this->em->getClassMetadata(Activity::class); $from = $this->getFromClauseCenter($args); - $where = $this->getWhereClause($context, $args); + [$where, $parameters] = $this->getWhereClause($context, $args); return [ 'id' => $metadataActivity->getTableName() @@ -71,7 +71,8 @@ final class ActivityACLAwareRepository 'date' => $metadataActivity->getTableName() .'.'.$metadataActivity->getColumnName('date'), 'FROM' => $from, - 'WHERE' => $where + 'WHERE' => $where, + 'parameters' => $parameters ]; } diff --git a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php index 52e1027a8..ff1d9ffb3 100644 --- a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php +++ b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php @@ -29,13 +29,13 @@ use Symfony\Component\Security\Core\Role\Role; use Doctrine\ORM\Mapping\ClassMetadata; use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Entity\Scope; +use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Timeline\TimelineSingleQuery; /** * Provide activity for inclusion in timeline * - * @author Julien Fastré - * @author Champs Libres - */ +*/ class TimelineActivityProvider implements TimelineProviderInterface { @@ -93,63 +93,37 @@ class TimelineActivityProvider implements TimelineProviderInterface */ public function fetchQuery($context, array $args) { - //$this->checkContext($context); - // if ('center' === $context) { - return $this->aclAwareRepository->queryTimelineIndexer($context, $args); + return TimelineSingleQuery::fromArray($this->aclAwareRepository + ->queryTimelineIndexer($context, $args)); } - $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); - - return array( + $metadataActivity = $this->em->getClassMetadata(Activity::class); + + [$where, $parameters] = $this->getWhereClauseForPerson($args['person']); + dump($where, $parameters); + return TimelineSingleQuery::fromArray([ 'id' => $metadataActivity->getTableName() .'.'.$metadataActivity->getColumnName('id'), 'type' => 'activity', 'date' => $metadataActivity->getTableName() .'.'.$metadataActivity->getColumnName('date'), - 'FROM' => $this->getFromClause($metadataActivity, $metadataPerson), - 'WHERE' => $this->getWhereClause($metadataActivity, $metadataPerson, - $args['person']) - ); - } - - private function getFromClause(string $context) - { - switch ($context) { - case 'person': - return $this->getFromClausePerson($metadataActivity, $metadataPerson); - } - } - - private function getWhereClause(string $context, array $args) - { - switch ($context) { - case 'person': - return $this->getWhereClause($args['person']); - } - } - - /** - * - * @var $centers array|Center[] - */ - private function getWhereClauseForCenter(array $centers) - { - $clause = ""; - $role = new Role('CHILL_ACTIVITY_SEE'); - - + 'FROM' => $this->getFromClausePerson($args['person']), + 'WHERE' => $where, + 'parameters' => $parameters + ]); } private function getWhereClauseForPerson(Person $person) { + $parameters = []; $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); + $associationMapping = $metadataActivity->getAssociationMapping('person'); $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); $role = new Role('CHILL_ACTIVITY_SEE'); $reachableCenters = $this->helper->getReachableCenters($this->user, $role); - $associationMapping = $metadataActivity->getAssociationMapping('person'); if (count($reachableCenters) === 0) { return 'FALSE = TRUE'; @@ -157,31 +131,41 @@ class TimelineActivityProvider implements TimelineProviderInterface // we start with activities having the person_id linked to person // (currently only context "person" is supported) - $whereClause = sprintf('%s = %d', - $associationMapping['joinColumns'][0]['name'], - $person->getId()); + $whereClause = sprintf(' {activity.person_id} = ? '); + $parameters[] = $person->getId(); // we add acl (reachable center and scopes) - $centerAndScopeLines = array(); + $centerAndScopeClauses = []; foreach ($reachableCenters as $center) { - $reachablesScopesId = array_map( - function(Scope $scope) { return $scope->getId(); }, - $this->helper->getReachableScopes($this->user, $role, - $person->getCenter()) - ); - - $centerAndScopeLines[] = sprintf('(%s = %d AND %s IN (%s))', - $metadataPerson->getTableName().'.'. - $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'], - $center->getId(), - $metadataActivity->getTableName().'.'. - $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'], - implode(',', $reachablesScopesId)); - + $parameters[] = $center->getId(); + $scopes_ids = []; + $reachableScopes = $this->helper->getReachableScopes($this->user, $role, $person->getCenter()); + foreach ($reachableScopes as $scope) { + $scopes_ids[] = '?'; + $parameters[] = $scope->getId(); + } + $centerAndScopeClauses[] = \strtr( + '( {person.center_id} = ? AND {activity.scope_id} IN ({scopes_ids})) ', + [ + '{scopes_ids}' => \implode(", ", $scopes_ids) + ] + ); } - $whereClause .= ' AND ('.implode(' OR ', $centerAndScopeLines).')'; - - return $whereClause; + $whereClause .= ' AND ('.\implode(' OR ', $centerAndScopeClauses).' ) '; + + return [ + \strtr( + $whereClause, + [ + '{activity.person_id}' => $associationMapping['joinColumns'][0]['name'], + '{person.center_id}' => $metadataPerson->getTableName().'.'. + $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'], + '{activity.scope_id}' => $metadataActivity->getTableName().'.'. + $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'], + ] + ), + $parameters + ]; } private function getFromClausePerson() @@ -199,21 +183,6 @@ class TimelineActivityProvider implements TimelineProviderInterface ; } - private function getFromClauseCenter() - { - $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); - $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); - $associationMapping = $metadataActivity->getAssociationMapping('person'); - - return $metadataActivity->getTableName().' JOIN ' - .$metadataPerson->getTableName().' ON ' - .$metadataPerson->getTableName().'.'. - $associationMapping['joinColumns'][0]['referencedColumnName'] - .' = ' - .$associationMapping['joinColumns'][0]['name'] - ; - } - /** * * {@inheritDoc} diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php b/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php index 2e4102af6..bd1a4cc1a 100644 --- a/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php @@ -201,10 +201,13 @@ class TimelineBuilder implements ContainerAwareInterface /** * return the SQL SELECT query as a string, * - * @return string + * @return array: first parameter is the sql string, second an array with parameters */ - private function buildSelectQuery(array $data): array + private function buildSelectQuery($data): array { + return [$data->buildSql(), $data->getParameters()]; + + // dead code $parameters = []; $sql = sprintf( diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php b/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php new file mode 100644 index 000000000..cf8dc085e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php @@ -0,0 +1,155 @@ +id = $id; + $this->date = $date; + $this->key = $key; + $this->from = $from; + $this->where = $where; + $this->parameters = $parameters; + } + + public static function fromArray(array $a) + { + return new TimelineSingleQuery( + $a['id'], + $a['date'], + $a['type'] ?? $a['key'], + $a['FROM'] ?? $a['from'], + $a['WHERE'] ?? $a['where'], + $a['parameters']); + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + + return $this; + } + + public function getDate(): string + { + return $this->date; + } + + public function setDate(string $date): self + { + $this->date = $date; + + return $this; + } + + public function getKey(): string + { + return $this->key; + } + + public function setKey(string $key): self + { + $this->key = $key; + + return $this; + } + + public function getFrom(): string + { + return $this->from; + } + + public function setFrom(string $from): self + { + $this->from = $from; + + return $this; + } + + public function getWhere(): string + { + return $this->where; + } + + public function setWhere(string $where): self + { + $this->where = $where; + + return $this; + } + + public function getParameters(): array + { + return $this->parameters; + } + + public function setParameters(array $parameters): self + { + $this->parameters = $parameters; + + return $this; + } + + public function setDistinct(bool $distinct): self + { + $this->distinct = $distinct; + + return $this; + } + + public function isDistinct(): bool + { + return $this->distinct; + } + + public function buildSql(): string + { + $parameters = []; + + $sql = \strtr( + 'SELECT {distinct} {id} AS id, ' + . '{date} AS "date", ' + . "'{key}' AS type " + . 'FROM {from} ' + . 'WHERE {where}', + [ + '{distinct}' => $this->distinct ? 'DISTINCT' : '', + '{id}' => $this->getId(), + '{date}' => $this->getDate(), + '{key}' => $this->getKey(), + '{from}' => $this->getFrom(), + '{where}' => $this->getWhere(), + ] + ); + + return $sql; + } +} From 9b1a66c992c902d524afdf4b1c33567a66b2b7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 25 May 2021 09:48:19 +0200 Subject: [PATCH 12/81] fix query in timeline in activity and person bundle --- .../Timeline/TimelineActivityProvider.php | 48 +++++++------------ .../Timeline/TimelineBuilder.php | 2 - .../Timeline/TimelineSingleQuery.php | 12 ++--- .../AbstractTimelineAccompanyingPeriod.php | 15 +++--- .../TimelineAccompanyingPeriodClosing.php | 16 +++---- .../TimelineAccompanyingPeriodOpening.php | 8 ++-- 6 files changed, 43 insertions(+), 58 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php index ff1d9ffb3..a4dbdfad3 100644 --- a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php +++ b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php @@ -101,7 +101,7 @@ class TimelineActivityProvider implements TimelineProviderInterface $metadataActivity = $this->em->getClassMetadata(Activity::class); [$where, $parameters] = $this->getWhereClauseForPerson($args['person']); - dump($where, $parameters); + return TimelineSingleQuery::fromArray([ 'id' => $metadataActivity->getTableName() .'.'.$metadataActivity->getColumnName('id'), @@ -119,49 +119,33 @@ class TimelineActivityProvider implements TimelineProviderInterface $parameters = []; $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity'); $associationMapping = $metadataActivity->getAssociationMapping('person'); - $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person'); - $role = new Role('CHILL_ACTIVITY_SEE'); - $reachableCenters = $this->helper->getReachableCenters($this->user, - $role); - - if (count($reachableCenters) === 0) { - return 'FALSE = TRUE'; - } - - // we start with activities having the person_id linked to person - // (currently only context "person" is supported) - $whereClause = sprintf(' {activity.person_id} = ? '); + $reachableScopes = $this->helper->getReachableScopes($this->user, + $role, $person->getCenter()); + $whereClause = sprintf(' {activity.person_id} = ? AND {activity.scope_id} IN ({scopes_ids}) '); + $scopes_ids = []; + + // first parameter: activity.person_id $parameters[] = $person->getId(); - - // we add acl (reachable center and scopes) - $centerAndScopeClauses = []; - foreach ($reachableCenters as $center) { - $parameters[] = $center->getId(); - $scopes_ids = []; - $reachableScopes = $this->helper->getReachableScopes($this->user, $role, $person->getCenter()); - foreach ($reachableScopes as $scope) { - $scopes_ids[] = '?'; - $parameters[] = $scope->getId(); + + // loop on reachable scopes + foreach ($reachableScopes as $scope) { + if (\in_array($scope->getId(), $scopes_ids)) { + continue; } - $centerAndScopeClauses[] = \strtr( - '( {person.center_id} = ? AND {activity.scope_id} IN ({scopes_ids})) ', - [ - '{scopes_ids}' => \implode(", ", $scopes_ids) - ] - ); + $scopes_ids[] = '?'; + $parameters[] = $scope->getId(); } - $whereClause .= ' AND ('.\implode(' OR ', $centerAndScopeClauses).' ) '; return [ \strtr( $whereClause, [ '{activity.person_id}' => $associationMapping['joinColumns'][0]['name'], - '{person.center_id}' => $metadataPerson->getTableName().'.'. - $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'], '{activity.scope_id}' => $metadataActivity->getTableName().'.'. $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'], + '{scopes_ids}' => \implode(", ", $scopes_ids) +, ] ), $parameters diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php b/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php index bd1a4cc1a..de2ae9975 100644 --- a/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php @@ -28,8 +28,6 @@ use Doctrine\ORM\NativeQuery; /** * Build timeline - * - * @author Julien Fastré */ class TimelineBuilder implements ContainerAwareInterface { diff --git a/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php b/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php index cf8dc085e..e7e456a80 100644 --- a/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php +++ b/src/Bundle/ChillMainBundle/Timeline/TimelineSingleQuery.php @@ -38,12 +38,12 @@ class TimelineSingleQuery public static function fromArray(array $a) { return new TimelineSingleQuery( - $a['id'], - $a['date'], - $a['type'] ?? $a['key'], - $a['FROM'] ?? $a['from'], - $a['WHERE'] ?? $a['where'], - $a['parameters']); + $a['id'] ?? null, + $a['date'] ?? null, + $a['type'] ?? $a['key'] ?? null, + $a['FROM'] ?? $a['from'] ?? null, + $a['WHERE'] ?? $a['where'] ?? null, + $a['parameters'] ?? null); } public function getId(): string diff --git a/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php index 6b5b0cfd8..d5778280a 100644 --- a/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Timeline/AbstractTimelineAccompanyingPeriod.php @@ -28,6 +28,7 @@ use Chill\MainBundle\Entity\Center; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Component\Security\Core\Security; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Timeline\TimelineSingleQuery; /** * Provide method to build timeline for accompanying periods @@ -88,14 +89,16 @@ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInt } $metadata = $this->em - ->getClassMetadata(AccompanyingPeriodParticipation::class) + ->getClassMetadata(AccompanyingPeriod::class) ; - - return array( + [$where, $parameters] = $this->buildWhereClause($context, $args); + + return TimelineSingleQuery::fromArray([ 'id' => "{$metadata->getTableName()}.{$metadata->getColumnName('id')}", 'FROM' => $this->buildFromClause($context), - 'WHERE' => $this->buildWhereClause($context, $args) - ); + 'WHERE' => $where, + 'parameters' => $parameters + ]); } private function buildFromClause($context) @@ -130,9 +133,9 @@ abstract class AbstractTimelineAccompanyingPeriod implements TimelineProviderInt $join = $participation->getAssociationMapping('person')['joinColumns'][0]; $person = $this->em->getClassMetadata(Person::class); $joinCenter = $person->getAssociationMapping('center')['joinColumns'][0]; - $allowedCenters = $this->authorizationHelper->filterReachableCenters($this->security->getUser(), $args['centers'], PersonVoter::SEE); if ($context === 'center') { + $allowedCenters = $this->authorizationHelper->filterReachableCenters($this->security->getUser(), $args['centers'], PersonVoter::SEE); $params = []; $questionMarks = []; $query = "{$person->getTableName()}.{$joinCenter['name']} IN ("; diff --git a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php index 2e3d953fa..c873b6405 100644 --- a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php +++ b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodClosing.php @@ -25,8 +25,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; /** * Provide information for opening periods to timeline - * - * @author Julien Fastré */ class TimelineAccompanyingPeriodClosing extends AbstractTimelineAccompanyingPeriod { @@ -49,13 +47,15 @@ class TimelineAccompanyingPeriodClosing extends AbstractTimelineAccompanyingPeri $metadata = $this->em ->getClassMetadata(AccompanyingPeriod::class); - $data = $this->basicFetchQuery($context, $args); - - $data['type'] = 'accompanying_period_closing'; - $data['date'] = $metadata->getColumnName('closingDate'); - $data['WHERE'] = $this->buildWhereClause($context, $args); + $query = $this->basicFetchQuery($context, $args); + [$where, $parameters] = $this->buildWhereClause($context, $args); + $query->setKey('accompanying_period_closing') + ->setDate($metadata->getColumnName('closingDate')) + ->setWhere($where) + ->setParameters($parameters) + ; - return $data; + return $query; } protected function buildWhereClause($context, array $args): array diff --git a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php index 5d3222789..04b3df5b8 100644 --- a/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php +++ b/src/Bundle/ChillPersonBundle/Timeline/TimelineAccompanyingPeriodOpening.php @@ -47,12 +47,12 @@ class TimelineAccompanyingPeriodOpening extends AbstractTimelineAccompanyingPeri $metadata = $this->em ->getClassMetadata(AccompanyingPeriod::class); - $data = $this->basicFetchQuery($context, $args); + $query = $this->basicFetchQuery($context, $args); - $data['type'] = 'accompanying_period_opening'; - $data['date'] = $metadata->getColumnName('openingDate'); + $query->setKey('accompanying_period_opening') + ->setDate($metadata->getColumnName('openingDate')); - return $data; + return $query; } /** From 0afcf3d79e6cde2e3ff91cff528372e12a870cd4 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 12 May 2021 11:52:05 +0200 Subject: [PATCH 13/81] address selection: move marker on the leaflet map on update map --- .../public/vuejs/_components/AddAddress/AddressMap.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue index 5b616819d..2528f8e23 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/AddressMap.vue @@ -11,6 +11,7 @@ import markerIconPng from 'leaflet/dist/images/marker-icon.png' import 'leaflet/dist/leaflet.css'; let map; +let marker; export default { name: 'AddressMap', @@ -32,11 +33,12 @@ export default { iconUrl: markerIconPng, }); - L.marker([48.8589, 2.3469], {icon: markerIcon}).addTo(map); + marker = L.marker([48.8589, 2.3469], {icon: markerIcon}).addTo(map); }, update() { console.log('update map with : ', this.address.addressMap.center) + marker.setLatLng(this.address.addressMap.center); map.setView(this.address.addressMap.center, 12); } }, From fee37b5af3b51e4f68a07a3973b74aee17cad943 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 12 May 2021 12:21:19 +0200 Subject: [PATCH 14/81] address selection: use API points for Country and PostalCode + remove obsolet Address Controller --- .../Controller/AddressController.php | 63 ---------------- .../Resources/public/vuejs/_api/AddAddress.js | 32 ++++---- .../public/vuejs/_components/AddAddress.vue | 74 +++++++++---------- .../AddAddress/CountrySelection.vue | 16 ++-- 4 files changed, 63 insertions(+), 122 deletions(-) delete mode 100644 src/Bundle/ChillMainBundle/Controller/AddressController.php diff --git a/src/Bundle/ChillMainBundle/Controller/AddressController.php b/src/Bundle/ChillMainBundle/Controller/AddressController.php deleted file mode 100644 index 1aeb39062..000000000 --- a/src/Bundle/ChillMainBundle/Controller/AddressController.php +++ /dev/null @@ -1,63 +0,0 @@ -json($address); - default: - throw new BadRequestException('Unsupported format'); - } - } - - - /** - * Get API Data for showing endpoint - * - * @Route( - * "/{_locale}/main/api/1.0/address-reference/{address_reference_id}/show.{_format}", - * name="chill_main_address_reference_api_show" - * ) - * @ParamConverter("addressReference", options={"id": "address_reference_id"}) - */ - public function showAddressReference(AddressReference $addressReference, $_format): Response - { - // TODO check ACL ? - switch ($_format) { - case 'json': - return $this->json($addressReference); - default: - throw new BadRequestException('Unsupported format'); - } - - } -} diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js index 4de0fcc19..2e80572b7 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_api/AddAddress.js @@ -1,26 +1,32 @@ /* * Endpoint countries GET -* TODO +* method GET, get Country Object +* @returns {Promise} a promise containing all Country object */ const fetchCountries = () => { console.log('<<< fetching countries'); - return [ - {id: 1, name: 'France', countryCode: 'FR'}, - {id: 2, name: 'Belgium', countryCode: 'BE'} - ]; + + const url = `/api/1.0/main/country.json`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); }; /* -* Endpoint cities GET +* Endpoint cities GET * TODO */ -const fetchCities = (country) => { +const fetchCities = (country) => { console.log('<<< fetching cities for', country); - return [ - {id: 1, name: 'Bruxelles', code: '1000', country: 'BE'}, - {id: 2, name: 'Aisne', code: '85045', country: 'FR'}, - {id: 3, name: 'Saint-Gervais', code: '85230', country: 'FR'} - ]; + //TODO use country + const url = `/api/1.0/main/postal-code.json`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); }; /* @@ -30,7 +36,7 @@ const fetchCities = (country) => { */ const fetchReferenceAddresses = (city) => { console.log('<<< fetching references addresses for', city); // city n'est pas utilisé pour le moment - + const url = `/api/1.0/main/address-reference.json`; return fetch(url) .then(response => { diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue index 55cf2f098..f304f3c45 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue @@ -13,14 +13,14 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue index 253337ef6..87bcf79a4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue @@ -8,8 +8,7 @@ :custom-label="transName" :placeholder="$t('select_country')" :options="countries" - @update:model-value="selectCountry" - :model-value="value"> + @select="selectCountry"> From 165012b30205f906f722ef557270e09e96f14fbd Mon Sep 17 00:00:00 2001 From: nobohan Date: Fri, 28 May 2021 10:14:54 +0200 Subject: [PATCH 47/81] address: POST an address --- .../ChillMainBundle/Doctrine/Model/Point.php | 6 ++-- .../Resources/public/vuejs/Address/App.vue | 5 +-- .../Serializer/Normalizer/PointNormalizer.php | 34 +++++++++++++++++++ .../config/services/serializer.yaml | 9 +++-- 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php b/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php index 43c21ae59..2e6f83a69 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php @@ -9,11 +9,11 @@ use \JsonSerializable; * */ class Point implements JsonSerializable { - private float $lat; - private float $lon; + private ?float $lat = null; + private ?float $lon = null; public static string $SRID = '4326'; - private function __construct(float $lon, float $lat) + private function __construct(?float $lon, ?float $lat) { $this->lat = $lat; $this->lon = $lon; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue index 8276f531f..eddc93fe7 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue @@ -33,8 +33,6 @@ export default { methods: { addNewAddress({ address, modal }) { console.log('@@@ CLICK button addNewAdress', address); - const lon = address.selected.address.point.coordinates[0]; - const lat = address.selected.address.point.coordinates[1]; const newAddress = { 'isNoAddress': address.isNoAddress, 'street': address.selected.address.street, @@ -47,8 +45,7 @@ export default { 'buildingName': address.buildingName, 'distribution': address.distribution, 'extra': address.extra, - //'point': {'lon': lon, 'lat': lat} WIP - 'point': `SRID=4326;POINT(${lon}, ${lat})` + 'point': address.selected.address.point.coordinates }; this.$store.dispatch('addAddress', newAddress); modal.showModal = false; diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php new file mode 100644 index 000000000..210e85fb7 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PointNormalizer.php @@ -0,0 +1,34 @@ + Date: Fri, 28 May 2021 12:31:08 +0200 Subject: [PATCH 48/81] addresses form: add default country value --- .../public/vuejs/_components/AddAddress.vue | 5 +++-- .../_components/AddAddress/CountrySelection.vue | 16 ++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue index 33cf6d8a5..ad6e32c5d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue @@ -168,12 +168,13 @@ export default { } } }, + mounted() { + this.getCountries(); + }, methods: { openModal() { this.modal.showModal = true; this.resetAll(); - this.getCountries(); - //this.$nextTick(function() { // this.$refs.search.focus(); // positionner le curseur à l'ouverture de la modale //}) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue index 87bcf79a4..44009cdc4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress/CountrySelection.vue @@ -22,24 +22,28 @@ export default { props: ['address', 'getCities'], data() { return { - //value: this.address.loaded.countries.filter(c => c.countryCode === 'FR')[0], - value: null + value: this.address.loaded.countries.filter(c => c.countryCode === 'FR')[0] } }, methods: { + init() { + if (this.value !== undefined) { + this.getCities(this.value); + } + }, transName ({ name }) { return name.fr //TODO multilang }, selectCountry(value) { + console.log(value); this.address.selected.country = value; this.getCities(value); }, }, + mounted(){ + this.init() + }, computed: { - // defaultCountry() { - // const countries = this.address.loaded.countries; - // return countries.filter(c => c.countryCode === 'FR')[0]; - // }, countries() { const countries = this.address.loaded.countries; let orderedCountries = []; From 74520330b1d9555a9ce3c1d26244576b2986d617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 13:25:37 +0200 Subject: [PATCH 49/81] add position for household and prefix tables names --- .../migrations/Version20210528090000.php | 29 ++++++ .../Entity/Household/Household.php | 3 + .../Entity/Household/HouseholdMembers.php | 7 +- .../Entity/Household/Position.php | 93 +++++++++++++++++++ src/Bundle/ChillPersonBundle/Household | 0 .../Household/PositionRepository.php | 50 ++++++++++ .../migrations/Version20210528092625.php | 62 +++++++++++++ .../migrations/Version20210528111624.php | 39 ++++++++ 8 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20210528090000.php create mode 100644 src/Bundle/ChillPersonBundle/Entity/Household/Position.php create mode 100644 src/Bundle/ChillPersonBundle/Household create mode 100644 src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php new file mode 100644 index 000000000..de3c7336a --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php @@ -0,0 +1,29 @@ +addSql('CREATE EXTENSION IF NOT EXISTS btree_gist'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP EXTENSION btree_gist'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index 4e8787a96..420325d6d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -8,6 +8,9 @@ use Chill\MainBundle\Entity\Address; /** * @ORM\Entity + * @ORM\Table( + * name="chill_person_household" + * ) */ class Household { diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php index 5d16649a8..d39034e32 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php @@ -5,9 +5,14 @@ namespace Chill\PersonBundle\Entity\Household; use Doctrine\ORM\Mapping as ORM; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\Position; + /** * @ORM\Entity + * @ORM\Table( + * name="chill_person_household_members" + * ) */ class HouseholdMembers { @@ -19,7 +24,7 @@ class HouseholdMembers private $id; /** - * @ORM\Column(type="string", length=255, nullable=true) + * @ORM\ManyToOne(targetEntity=Position::class) */ private $position; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php new file mode 100644 index 000000000..60e19b25a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -0,0 +1,93 @@ +id; + } + + public function getLabel(): ?array + { + return $this->label; + } + + public function setLabel(array $label): self + { + $this->label = $label; + + return $this; + } + + public function getShareHouseHold(): ?bool + { + return $this->shareHouseHold; + } + + public function setShareHouseHold(bool $shareHouseHold): self + { + $this->shareHouseHold = $shareHouseHold; + + return $this; + } + + public function getAllowHolder(): ?bool + { + return $this->allowHolder; + } + + public function setAllowHolder(bool $allowHolder): self + { + $this->allowHolder = $allowHolder; + + return $this; + } + + public function getOrdering(): ?float + { + return $this->ordering; + } + + public function setOrdering(float $ordering): self + { + $this->ordering = $ordering; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Household b/src/Bundle/ChillPersonBundle/Household new file mode 100644 index 000000000..e69de29bb diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php new file mode 100644 index 000000000..0ca9ef10e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('p.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Position + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php new file mode 100644 index 000000000..5d1909424 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php @@ -0,0 +1,62 @@ +addSql('ALTER TABLE householdmembers DROP CONSTRAINT fk_4d1fb288e79ff843'); + $this->addSql('ALTER TABLE householdmembers DROP CONSTRAINT fk_4d1fb288217bbb47'); + + // rename tables + $this->addSql('ALTER TABLE householdmembers RENAME TO chill_person_household_members'); + $this->addSql('ALTER TABLE household RENAME TO chill_person_household'); + + // rename sequences + $this->addSql('ALTER SEQUENCE household_id_seq RENAME TO chill_person_household_id_seq'); + $this->addSql('ALTER SEQUENCE householdmembers_id_seq RENAME TO chill_person_household_members_id_seq'); + + // recreate constraints + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT FK_EEF5DED7217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT FK_EEF5DED7E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + + // create constraint 'householdmembers not overlaps' + $this->addSql('ALTER TABLE chill_person_household_members ADD CHECK (startdate < enddate)'); + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT '. + "household_members_not_overlaps EXCLUDE USING GIST( + -- extension btree_gist required to include comparaison with integer + person_id WITH =, + daterange(startdate, enddate) WITH && + ) WHERE (sharedhousehold IS TRUE)"); + + // rename constraints + $this->addSql('ALTER TABLE public.chill_person_household_to_addresses DROP CONSTRAINT fk_7109483e79ff843'); + $this->addSql('ALTER TABLE chill_person_household_to_addresses ADD CONSTRAINT FK_C28AF063E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + + // rename indexes + $this->addSql('ALTER INDEX idx_7109483e79ff843 RENAME TO IDX_C28AF063E79FF843'); + $this->addSql('ALTER INDEX idx_7109483f5b7af75 RENAME TO IDX_C28AF063F5B7AF75'); + $this->addSql('ALTER INDEX idx_4d1fb288e79ff843 RENAME TO IDX_EEF5DED7E79FF843'); + $this->addSql('ALTER INDEX idx_4d1fb288217bbb47 RENAME TO IDX_EEF5DED7217BBB47'); + } + + public function down(Schema $schema): void + { + $this->throwIrreversibleMigrationException("the down method is not implemented"); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php new file mode 100644 index 000000000..2228d42c3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php @@ -0,0 +1,39 @@ +addSql('CREATE SEQUENCE chill_person_household_position_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_person_household_position (id INT NOT NULL, label JSON NOT NULL, shareHouseHold BOOLEAN NOT NULL, allowHolder BOOLEAN NOT NULL, ordering DOUBLE PRECISION NOT NULL, PRIMARY KEY(id))'); + + $this->addSql('ALTER TABLE chill_person_household_members ADD position_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members DROP "position"'); + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT FK_EEF5DED7DD842E46 FOREIGN KEY (position_id) REFERENCES chill_person_household_position (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_EEF5DED7DD842E46 ON chill_person_household_members (position_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_household_members DROP CONSTRAINT FK_EEF5DED7DD842E46'); + $this->addSql('DROP SEQUENCE chill_person_household_position_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_person_household_position'); + $this->addSql('ALTER TABLE chill_person_household_members ADD "position" VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members DROP position_id'); + } +} From 94bcbac06a64847be6da84e17fafcba9dd7b21f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 13:55:06 +0200 Subject: [PATCH 50/81] add extension btree_gist to postgres --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86e65c390..6973ff6d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ cache: before_script: # add extensions to postgres - - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm;" + - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gist;" # Install and run Composer - curl -sS https://getcomposer.org/installer | php - php -d memory_limit=2G composer.phar install From 87ba68971c5bdec1274762ec4b60ae05cdc25055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 16:41:37 +0200 Subject: [PATCH 51/81] first impl of person Mover + add fixtures --- .../DataFixtures/ORM/LoadHousehold.php | 101 ++++++++++++++++++ .../ORM/LoadHouseholdPosition.php | 38 +++++++ .../ChillPersonExtension.php | 1 + ...useholdMembers.php => HouseholdMember.php} | 68 ++++++++---- .../Entity/Household/Position.php | 4 +- .../ChillPersonBundle/Entity/Person.php | 22 ++++ src/Bundle/ChillPersonBundle/Household | 0 .../Household/MoveMembers.php | 80 ++++++++++++++ .../Household/MoveMembersFactory.php | 27 +++++ .../config/services/fixtures.yaml | 1 + .../config/services/household.yaml | 3 + .../migrations/Version20210528132405.php | 31 ++++++ .../migrations/Version20210528142121.php | 29 +++++ 13 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php create mode 100644 src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php rename src/Bundle/ChillPersonBundle/Entity/Household/{HouseholdMembers.php => HouseholdMember.php} (60%) delete mode 100644 src/Bundle/ChillPersonBundle/Household create mode 100644 src/Bundle/ChillPersonBundle/Household/MoveMembers.php create mode 100644 src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php create mode 100644 src/Bundle/ChillPersonBundle/config/services/household.yaml create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php new file mode 100644 index 000000000..a8c3c73be --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php @@ -0,0 +1,101 @@ +movementFactory = $movementFactory; + $this->em = $em; + } + + public function load(ObjectManager $manager) + { + $this->preparePersonIds(); + + for ($i=0; $i < self::NUMBER_OF_HOUSEHOLD; $i++) { + $household = new Household(); + $manager->persist($household); + + $movement = $this->movementFactory->createMovement($household); + + // load adults + $k = 0; + foreach ($this->getRandomPersons(1, 3) as $person) { + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $position = $this->getReference(LoadHouseholdPosition::ADULT); + + $movement->addMovement($date, $person, $position, $k === 0, "self generated"); + $k++; + } + + // load children + foreach ($this->getRandomPersons(0, 3) as $person) { + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $position = $this->getReference(LoadHouseholdPosition::CHILD); + + $movement->addMovement($date, $person, $position, $k === 0, "self generated"); + $k++; + } + + foreach ($movement->getPersistable() as $obj) { + print($obj->getStartDate()->format('Y-m-d')); + $manager->persist($obj); + } + } + + $manager->flush(); + } + + private function preparePersonIds() + { + $this->personIds = $this->em + ->createQuery('SELECT p.id FROM '.Person::class.' p '. + 'JOIN p.center c '. + 'WHERE c.name = :center ' + ) + ->setParameter('center', 'Center A') + ->getScalarResult() + ; + \shuffle($this->personIds); + } + + private function getRandomPersons(int $min, int $max) + { + $nb = \random_int($min, $max); + + for ($i=0; $i < $nb; $i++) { + $personId = \array_pop($this->personIds)['id']; + $persons[] = $this->em->getRepository(Person::class) + ->find($personId) + ; + } + + return $persons ?? []; + } + + public function getDependencies() + { + return [ + LoadPeople::class, + LoadHouseholdPosition::class + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php new file mode 100644 index 000000000..cf0bf3de1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php @@ -0,0 +1,38 @@ +setLabel([ "fr" => $name ]) + ->setAllowHolder($allowHolder) + ->setShareHousehold($share) + ->setOrdering($ordering) + ; + + $manager->persist($position); + $this->addReference($ref, $position); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 27721012d..a28887c68 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -74,6 +74,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/form.yaml'); $loader->load('services/templating.yaml'); $loader->load('services/alt_names.yaml'); + $loader->load('services/household.yaml'); // We can get rid of this file when the service 'chill.person.repository.person' is no more used. // We should use the PersonRepository service instead of a custom service name. $loader->load('services/repository.yaml'); diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php similarity index 60% rename from src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php rename to src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php index d39034e32..89a9f3299 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php @@ -14,7 +14,7 @@ use Chill\PersonBundle\Entity\Household\Position; * name="chill_person_household_members" * ) */ -class HouseholdMembers +class HouseholdMember { /** * @ORM\Id @@ -26,27 +26,32 @@ class HouseholdMembers /** * @ORM\ManyToOne(targetEntity=Position::class) */ - private $position; + private ?Position $position = null; /** - * @ORM\Column(type="date") + * @ORM\Column(type="date", nullable=true, options={"default": null}) */ - private $startDate; + private ?\DateTimeImmutable $startDate = null; /** - * @ORM\Column(type="date") + * @ORM\Column(type="date", nullable= true, options={"default": null}) */ - private $endDate; + private ?\DateTimeImmutable $endDate = null; /** * @ORM\Column(type="string", length=255, nullable=true) */ - private $comment; + private ?string $comment = NULL; /** * @ORM\Column(type="boolean") */ - private $sharedHousehold; + private bool $sharedHousehold = false; + + /** + * @ORM\Column(type="boolean", options={"default": false}) + */ + private bool $holder = false; /** * @@ -55,7 +60,7 @@ class HouseholdMembers * targetEntity="\Chill\PersonBundle\Entity\Person" * ) */ - private $person; + private ?Person $person = null; /** * @@ -64,21 +69,28 @@ class HouseholdMembers * targetEntity="\Chill\PersonBundle\Entity\Household\Household" * ) */ - private $household; + private ?Household $household = null; + public function getId(): ?int { return $this->id; } - public function getPosition(): ?string + public function getPosition(): ?Position { return $this->position; } - public function setPosition(?string $position): self + public function setPosition(Position $position): self { + if ($this->position instanceof Position) { + throw new \LogicException("The position is already set. You cannot change ". + "a position of a membership"); + } + $this->position = $position; + $this->sharedHousehold = $position->getShareHousehold(); return $this; } @@ -119,17 +131,11 @@ class HouseholdMembers return $this; } - public function getSharedHousehold(): ?bool + public function getShareHousehold(): ?bool { return $this->sharedHousehold; } - public function setSharedHousehold(bool $sharedHousehold): self - { - $this->sharedHousehold = $sharedHousehold; - - return $this; - } public function getPerson(): ?Person { @@ -138,8 +144,15 @@ class HouseholdMembers public function setPerson(?Person $person): self { + if ($this->person instanceof Person) { + throw new \LogicException("You cannot change person ". + "on a membership"); + } + $this->person = $person; + $person->addHouseholdParticipation($this); + return $this; } @@ -150,8 +163,25 @@ class HouseholdMembers public function setHousehold(?Household $household): self { + if ($this->household instanceof Household) { + throw new \LogicException("You cannot change household ". + "on a membership"); + } + $this->household = $household; return $this; } + + public function setHolder(bool $holder): self + { + $this->holder = $holder; + + return $this; + } + + public function isHolder(): bool + { + return $this->holder; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php index 60e19b25a..070399661 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -55,12 +55,12 @@ class Position return $this; } - public function getShareHouseHold(): ?bool + public function getShareHousehold(): ?bool { return $this->shareHouseHold; } - public function setShareHouseHold(bool $shareHouseHold): self + public function setShareHousehold(bool $shareHouseHold): self { $this->shareHouseHold = $shareHouseHold; diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index a4fb185fb..0d9bb7cf6 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -26,6 +26,7 @@ use ArrayIterator; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Country; use Chill\PersonBundle\Entity\MaritalStatus; +use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\Address; use DateTime; @@ -272,6 +273,14 @@ class Person implements HasCenterInterface */ private $fullnameCanonical; + /** + * @ORM\OneToMany( + * targetEntity=HouseholdMember::class, + * mappedBy="person" + * ) + */ + private Collection $householdParticipations; + /** * Person constructor. * @@ -284,6 +293,7 @@ class Person implements HasCenterInterface $this->addresses = new ArrayCollection(); $this->altNames = new ArrayCollection(); $this->otherPhoneNumbers = new ArrayCollection(); + $this->householdParticipations = new ArrayCollection(); if ($opening === null) { $opening = new \DateTime(); @@ -1180,4 +1190,16 @@ class Person implements HasCenterInterface $this->fullnameCanonical = $fullnameCanonical; return $this; } + + public function addHouseholdParticipation(HouseholdMember $member): self + { + $this->householdParticipations[] = $member; + + return $this; + } + + public function getHouseholdParticipations(): Collection + { + return $this->householdParticipations; + } } diff --git a/src/Bundle/ChillPersonBundle/Household b/src/Bundle/ChillPersonBundle/Household deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembers.php b/src/Bundle/ChillPersonBundle/Household/MoveMembers.php new file mode 100644 index 000000000..495c4122b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Household/MoveMembers.php @@ -0,0 +1,80 @@ +validation = $validator; + } + + public function toHousehold(Household $household): self + { + $this->household = $household; + + return $this; + } + + public function addMovement(\DateTimeInterface $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self + { + if (NULL === $this->household) { + throw new \LogicException("You must define a household first"); + } + + $membership = (new HouseholdMember()) + ->setStartDate($date) + ->setPerson($person) + ->setPosition($position) + ->setHolder($holder) + ->setHousehold($this->household) + ->setComment($comment) + ; + + if ($position->getShareHousehold()) { + foreach ($person->getHouseholdParticipations() as $participation) { + if (FALSE === $participation->getShareHousehold()) { + continue; + } + + if ($participation === $membership) { + continue; + } + + if ($participation->getEndDate() === NULL || $participation->getEndDate() > $date) { + $participation->setEndDate($date); + $this->membershipsAffected[] = $participation; + } + } + } + + $this->membershipsAffected[] = $membership; + $this->persistables[] = $membership; + + return $this; + } + + public function validate(): ConstraintViolationListInterface + { + + } + + public function getPersistable(): array + { + return $this->persistables; + } +} diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php b/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php new file mode 100644 index 000000000..6a3910fda --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php @@ -0,0 +1,27 @@ +validator = $validator; + } + + public function createMovement(?Household $household): MoveMembers + { + $movement = new MoveMembers($this->validator); + + if ($household) { + $movement->toHousehold($household); + } + + return $movement; + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml b/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml index a6becd555..72bf899f4 100644 --- a/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml @@ -1,5 +1,6 @@ services: Chill\PersonBundle\DataFixtures\ORM\: + autowire: true resource: ../../DataFixtures/ORM tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillPersonBundle/config/services/household.yaml b/src/Bundle/ChillPersonBundle/config/services/household.yaml new file mode 100644 index 000000000..99c472d91 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/household.yaml @@ -0,0 +1,3 @@ +services: + Chill\PersonBundle\Household\MoveMembersFactory: + autowire: true diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php new file mode 100644 index 000000000..f9e94532a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_person_household_members ALTER startdate DROP NOT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members ALTER enddate DROP NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_household_members ALTER startDate SET NOT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members ALTER endDate SET NOT NULL'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php new file mode 100644 index 000000000..4eaf149af --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php @@ -0,0 +1,29 @@ +addSql('ALTER TABLE chill_person_household_members ADD holder BOOLEAN DEFAULT FALSE NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_household_members DROP COLUMN holder'); + } +} From edc86af659631a5b17619953161cae7f13d31e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 18:01:20 +0200 Subject: [PATCH 52/81] add tests for moving houshold --- .../Tests/Household/MoveMembersTest.php | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php new file mode 100644 index 000000000..91a1d550d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php @@ -0,0 +1,103 @@ +createMock(ValidatorInterface::class); + + $this->factory = new MoveMembersFactory($validator); + } + + public function testMovePersonWithSharedHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(true) + ; + $household1 = new Household(); + $household2 = new Household(); + $editor = $this->factory->createMovement($household1); + + $editor->addMovement( + \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), + $person, + $position); + + $this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations()); + $this->assertEquals(1, $person->getHouseholdParticipations()->count()); + + $membership1 = $person->getHouseholdParticipations()->first(); + $this->assertSame($household1, $membership1->getHousehold()); + $this->assertNull($membership1->getEndDate()); + + // move to another household + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); + $editor = $this->factory->createMovement($household2); + $editor->addMovement( + $date, + $person, + $position); + + $this->assertEquals(2, $person->getHouseholdParticipations()->count()); + + $membership2 = $person->getHouseholdParticipations()->last(); + $this->assertSame($household2, $membership2->getHousehold()); + $this->assertNull($membership2->getEndDate()); + $this->assertNotNull($membership1->getEndDate(), + "assert that the membership1 is closed"); + $this->assertEquals($date, $membership1->getEndDate()); + } + + public function testMovePersonWithoutSharedHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(false) + ; + $household1 = new Household(); + $household2 = new Household(); + $editor = $this->factory->createMovement($household1); + + $editor->addMovement( + \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), + $person, + $position); + + $this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations()); + $this->assertEquals(1, $person->getHouseholdParticipations()->count()); + + $membership1 = $person->getHouseholdParticipations()->first(); + $this->assertSame($household1, $membership1->getHousehold()); + $this->assertNull($membership1->getEndDate()); + + // move to another household + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); + $editor = $this->factory->createMovement($household2); + $editor->addMovement( + $date, + $person, + $position); + + $this->assertEquals(2, $person->getHouseholdParticipations()->count()); + + $membership2 = $person->getHouseholdParticipations()->last(); + $this->assertNull($membership2->getEndDate()); + $this->assertSame($household2, $membership2->getHousehold()); + $this->assertNull($membership1->getEndDate(), + "assert that the membership1 is not closed"); + } +} From ace3b1969e5a94bc6008e5ab9ee91e877fe84485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 18:26:22 +0200 Subject: [PATCH 53/81] Rename membership edition and add more fixtures --- .../DataFixtures/ORM/LoadHousehold.php | 49 ++++++++++++++----- .../{MoveMembers.php => MembersEditor.php} | 12 ++--- .../Household/MembersEditorFactory.php | 21 ++++++++ .../Household/MoveMembersFactory.php | 27 ---------- ...eMembersTest.php => MembersEditorTest.php} | 16 +++--- .../config/services/household.yaml | 2 +- 6 files changed, 70 insertions(+), 57 deletions(-) rename src/Bundle/ChillPersonBundle/Household/{MoveMembers.php => MembersEditor.php} (92%) create mode 100644 src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php delete mode 100644 src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php rename src/Bundle/ChillPersonBundle/Tests/Household/{MoveMembersTest.php => MembersEditorTest.php} (88%) diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php index a8c3c73be..8dfca71f6 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php @@ -4,7 +4,7 @@ namespace Chill\PersonBundle\DataFixtures\ORM; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; -use Chill\PersonBundle\Household\MoveMembersFactory; +use Chill\PersonBundle\Household\MembersEditorFactory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; @@ -12,33 +12,53 @@ use Doctrine\Common\DataFixtures\DependentFixtureInterface; class LoadHousehold extends Fixture implements DependentFixtureInterface { - private MoveMembersFactory $movementFactory; + private MembersEditorFactory $editorFactory; private EntityManagerInterface $em; private CONST NUMBER_OF_HOUSEHOLD = 10; - public function __construct(MoveMembersFactory $movementFactory, EntityManagerInterface $em) + public function __construct(MembersEditorFactory $editorFactory, EntityManagerInterface $em) { - $this->movementFactory = $movementFactory; + $this->editorFactory = $editorFactory; $this->em = $em; } public function load(ObjectManager $manager) { + // generate two times the participation. This will lead to + // some movement in participation (same people in two differents + // households) + $this->preparePersonIds(); + $this->generateHousehold( + $manager, + \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + ); + + $this->preparePersonIds(); + + $this->generateHousehold( + $manager, + \DateTimeImmutable::createFromFormat('Y-m-d', '2015-01-01') + ); + + $manager->flush(); + } + + private function generateHousehold(ObjectManager $manager, \DateTimeImmutable $startDate) + { for ($i=0; $i < self::NUMBER_OF_HOUSEHOLD; $i++) { $household = new Household(); $manager->persist($household); - $movement = $this->movementFactory->createMovement($household); + $movement = $this->editorFactory->createEditor($household); // load adults $k = 0; foreach ($this->getRandomPersons(1, 3) as $person) { - $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') - ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); $position = $this->getReference(LoadHouseholdPosition::ADULT); $movement->addMovement($date, $person, $position, $k === 0, "self generated"); @@ -47,21 +67,26 @@ class LoadHousehold extends Fixture implements DependentFixtureInterface // load children foreach ($this->getRandomPersons(0, 3) as $person) { - $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') - ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); $position = $this->getReference(LoadHouseholdPosition::CHILD); $movement->addMovement($date, $person, $position, $k === 0, "self generated"); $k++; } + // load children out + foreach ($this->getRandomPersons(0, 2) as $person) { + $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $position = $this->getReference(LoadHouseholdPosition::CHILD_OUT); + + $movement->addMovement($date, $person, $position, $k === 0, "self generated"); + $k++; + } + foreach ($movement->getPersistable() as $obj) { - print($obj->getStartDate()->format('Y-m-d')); $manager->persist($obj); } } - - $manager->flush(); } private function preparePersonIds() diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembers.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php similarity index 92% rename from src/Bundle/ChillPersonBundle/Household/MoveMembers.php rename to src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 495c4122b..8dfd11b7b 100644 --- a/src/Bundle/ChillPersonBundle/Household/MoveMembers.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -10,7 +10,7 @@ use Chill\PersonBundle\Entity\Person; use Symfony\Component\Validator\Validator\ValidatorInterface; -class MoveMembers +class MembersEditor { private ValidatorInterface $validator; private Household $household; @@ -18,18 +18,12 @@ class MoveMembers private array $persistables = []; private array $memershipsAffected = []; - public function __construct(ValidatorInterface $validator) + public function __construct(ValidatorInterface $validator, Household $household) { $this->validation = $validator; + $this->household = $household; } - public function toHousehold(Household $household): self - { - $this->household = $household; - - return $this; - } - public function addMovement(\DateTimeInterface $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self { if (NULL === $this->household) { diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php new file mode 100644 index 000000000..611308b19 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php @@ -0,0 +1,21 @@ +validator = $validator; + } + + public function createEditor(Household $household): MembersEditor + { + return new MembersEditor($this->validator, $household); + } +} diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php b/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php deleted file mode 100644 index 6a3910fda..000000000 --- a/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php +++ /dev/null @@ -1,27 +0,0 @@ -validator = $validator; - } - - public function createMovement(?Household $household): MoveMembers - { - $movement = new MoveMembers($this->validator); - - if ($household) { - $movement->toHousehold($household); - } - - return $movement; - } -} diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php similarity index 88% rename from src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php rename to src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index 91a1d550d..ee3e2df86 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -5,21 +5,21 @@ namespace Chill\PersonBundle\Tests\Household; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Position; -use Chill\PersonBundle\Household\MoveMembersFactory; +use Chill\PersonBundle\Household\MembersEditorFactory; use Doctrine\Common\Collections\Collection; use Symfony\Component\Validator\Validator\ValidatorInterface; use PHPUnit\Framework\TestCase; -class MoveMembersTest extends TestCase +class MembersEditorTest extends TestCase { - private MoveMembersFactory $factory; + private MembersEditorFactory $factory; protected function setUp() { $validator = $this->createMock(ValidatorInterface::class); - $this->factory = new MoveMembersFactory($validator); + $this->factory = new MembersEditorFactory($validator); } public function testMovePersonWithSharedHousehold() @@ -30,7 +30,7 @@ class MoveMembersTest extends TestCase ; $household1 = new Household(); $household2 = new Household(); - $editor = $this->factory->createMovement($household1); + $editor = $this->factory->createEditor($household1); $editor->addMovement( \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), @@ -46,7 +46,7 @@ class MoveMembersTest extends TestCase // move to another household $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); - $editor = $this->factory->createMovement($household2); + $editor = $this->factory->createEditor($household2); $editor->addMovement( $date, $person, @@ -70,7 +70,7 @@ class MoveMembersTest extends TestCase ; $household1 = new Household(); $household2 = new Household(); - $editor = $this->factory->createMovement($household1); + $editor = $this->factory->createEditor($household1); $editor->addMovement( \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), @@ -86,7 +86,7 @@ class MoveMembersTest extends TestCase // move to another household $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); - $editor = $this->factory->createMovement($household2); + $editor = $this->factory->createEditor($household2); $editor->addMovement( $date, $person, diff --git a/src/Bundle/ChillPersonBundle/config/services/household.yaml b/src/Bundle/ChillPersonBundle/config/services/household.yaml index 99c472d91..300928c49 100644 --- a/src/Bundle/ChillPersonBundle/config/services/household.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/household.yaml @@ -1,3 +1,3 @@ services: - Chill\PersonBundle\Household\MoveMembersFactory: + Chill\PersonBundle\Household\MembersEditorFactory: autowire: true From 543f6b20dd4a45eb62df12cf447e58abbdf33c18 Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Mon, 31 May 2021 11:39:15 +0200 Subject: [PATCH 54/81] firstname and lastname in same column --- .../AccompanyingCourse/components/PersonsAssociated.vue | 3 +-- .../components/PersonsAssociated/PersonItem.vue | 6 ++++-- .../Resources/public/vuejs/AccompanyingCourse/js/i18n.js | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue index 7c61b066c..3ab9598c9 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue @@ -9,8 +9,7 @@ - - + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue index 4fa2caf12..840edf595 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/PersonItem.vue @@ -1,7 +1,9 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue index a37f93e7e..d50c1b1ff 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -65,9 +65,14 @@ @updateSelected="updateSelected"> - +
+ + +
+ @@ -84,6 +89,7 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index 37cee7852..5a4aa9de6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -15,22 +15,23 @@ {{ $t('item.type_thirdparty') }} - + + From 718d6c23757ae0e58a708d4fdf583b05149b91f3 Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Mon, 31 May 2021 19:25:37 +0200 Subject: [PATCH 57/81] add basic on-the-fly sub-components, to show/edit/create person/thirdparty --- .../public/modules/bootstrap/bootstrap.scss | 2 +- .../public/vuejs/_components/OnTheFly.vue | 34 +++++++-- .../vuejs/_components/OnTheFly/Create.vue | 73 +++++++++++++++++++ .../vuejs/_components/OnTheFly/Person.vue | 22 ++++++ .../vuejs/_components/OnTheFly/ThirdParty.vue | 22 ++++++ .../Resources/public/vuejs/_js/i18n.js | 4 +- .../vuejs/AccompanyingCourse/js/i18n.js | 2 +- 7 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/Create.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue diff --git a/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss b/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss index 8a77ac48f..cc9c090e2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss @@ -23,7 +23,7 @@ // @import "bootstrap/scss/button-group"; // @import "bootstrap/scss/input-group"; // @import "bootstrap/scss/custom-forms"; -// @import "bootstrap/scss/nav"; +@import "bootstrap/scss/nav"; // @import "bootstrap/scss/navbar"; // @import "bootstrap/scss/card"; // @import "bootstrap/scss/breadcrumb"; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly.vue index 3b7c67dc9..238b97851 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly.vue @@ -15,11 +15,29 @@ - @@ -28,13 +29,15 @@ + v-bind:action="action" + ref="castThirdparty"> @@ -44,7 +47,10 @@ class="sc-button bt-update"> @@ -55,8 +61,8 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue deleted file mode 100644 index 78bf4fdf9..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue index 768954778..b8dced534 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue @@ -16,6 +16,7 @@ export default { name: "OnTheFlyThirdParty", props: ['id', 'type', 'action'] } +// TODO move in ChillThirdpartyAssets diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js index e92a81e62..0f019c26b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js @@ -15,7 +15,21 @@ const personMessages = { person: { firstname: "Prénom", lastname: "Nom", - born: "né le ", + born: "né{e} le ", + center_id: "Identifiant du centre", + center_type: "Type de centre", + center_name: "Territoire", // vendée + phonenumber: "Téléphone", + mobilenumber: "Mobile", + altnames: "Autres noms", + gender: { + title: "Genre", + placeholder: "Choisissez le genre de l'usager", + woman: "Femme", + man: "Homme", + neuter: "Neutre", + } + }, error_only_one_person: "Une seule personne peut être sélectionnée !" } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 98f26987f..3fe5c56db 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -31,7 +31,7 @@ {% set gender = (p.person.gender == 'woman') ? 'fa-venus' : (p.person.gender == 'man') ? 'fa-mars' : 'fa-neuter' %} {% set genderTitle = (p.person.gender == 'woman') ? 'femme' : - (p.person.gender == 'homme') ? 'fa-mars' : 'neutre' %} + (p.person.gender == 'man') ? 'homme' : 'neutre' %} {{ born ~ ' le ' ~ p.person.birthdate|format_date('short') }}

From 9333b6d2df93761416718a56fe117221972cfe82 Mon Sep 17 00:00:00 2001 From: nobohan Date: Thu, 3 Jun 2021 10:22:20 +0200 Subject: [PATCH 72/81] address: correct customizeQuery --- .../Controller/AddressReferenceAPIController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php b/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php index 3e63c1b2a..cbe0ea0a5 100644 --- a/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php +++ b/src/Bundle/ChillMainBundle/Controller/AddressReferenceAPIController.php @@ -19,7 +19,7 @@ class AddressReferenceAPIController extends ApiController if ($request->query->has('postal_code')) { $qb->where('e.postcode = :postal_code') - ->setParameter('postal_code', $request->get('postal_code')); + ->setParameter('postal_code', $request->query->get('postal_code')); } } From a659263af654ea928d77322f1c8b84e7665e6ad7 Mon Sep 17 00:00:00 2001 From: nobohan Date: Thu, 3 Jun 2021 10:27:17 +0200 Subject: [PATCH 73/81] addresses: translations --- .../Resources/public/vuejs/Address/js/i18n.js | 6 +++--- .../Resources/public/vuejs/_components/AddAddress.vue | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js index 6a264c4f4..ba79e88a7 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/js/i18n.js @@ -1,12 +1,12 @@ const addressMessages = { fr: { - add_an_address: 'Ajouter une adresse', - select_an_address: 'Sélectionner une adresse', + add_an_address_title: 'Ajouter une adresse', + select_an_address_title: 'Sélectionner une adresse', fill_an_address: 'Compléter l\'adresse', select_country: 'Choisir le pays', select_city: 'Choisir une localité', select_address: 'Choisir une adresse', - create_address: 'Appuyer sur "Enter" pour créer une nouvelle adresse', + create_address: 'Appuyer sur "Entrée" pour créer une nouvelle adresse', isNoAddress: 'Pas d\'adresse complète', floor: 'Étage', corridor: 'Couloir', diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue index ad6e32c5d..0cfad7ac2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddAddress.vue @@ -1,6 +1,6 @@
{{ $t('persons_associated.firstname') }}{{ $t('persons_associated.lastname') }}{{ $t('persons_associated.name') }} {{ $t('persons_associated.startdate') }} {{ $t('persons_associated.enddate') }} {{ $t('action.actions') }}