diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php index 67f70db40..1e15490dc 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php @@ -4,15 +4,50 @@ namespace Chill\PersonBundle\Controller; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Serializer\Model\Collection; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Repository\Household\HouseholdRepository; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; class HouseholdApiController extends ApiController { + private HouseholdRepository $householdRepository; + + public function __construct(HouseholdRepository $householdRepository) + { + $this->householdRepository = $householdRepository; + } + public function householdAddressApi($id, Request $request, string $_format): Response { return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, [ 'groups' => [ 'read' ] ]); } + /** + * Find Household of people participating to the same AccompanyingPeriod + * + * @ParamConverter("person", options={"id" = "person_id"}) + */ + public function suggestHouseholdByAccompanyingPeriodParticipationApi(Person $person, string $_format) + { + // TODO add acl + + $count = $this->householdRepository->countByAccompanyingPeriodParticipation($person); + $paginator = $this->getPaginatorFactory()->create($count); + + if ($count === 0) { + $households = []; + } else { + $households = $this->householdRepository->findByAccompanyingPeriodParticipation($person, + $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); + } + + $collection = new Collection($households, $paginator); + + return $this->json($collection, Response::HTTP_OK, [], + [ "groups" => ["read"]]); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php index 89cd94ef5..2cfd12842 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php @@ -3,6 +3,7 @@ namespace Chill\PersonBundle\Controller; use Chill\PersonBundle\Entity\Household\Position; +use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; @@ -25,11 +26,17 @@ class HouseholdMemberController extends ApiController private TranslatorInterface $translator; + private AccompanyingPeriodRepository $periodRepository; - public function __construct(UrlGeneratorInterface $generator, TranslatorInterface $translator) + public function __construct( + UrlGeneratorInterface $generator, + TranslatorInterface $translator, + AccompanyingPeriodRepository $periodRepository + ) { $this->generator = $generator; $this->translator = $translator; + $this->periodRepository = $periodRepository; } /** @@ -144,8 +151,23 @@ class HouseholdMemberController extends ApiController 'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'), ]; + // context + if ($request->query->has('accompanying_period_id')) { + $period = $this->periodRepository->find( + $request->query->getInt('accompanying_period_id') + ); + + if ($period === null) { + throw $this->createNotFoundException('period not found'); + } + + // TODO add acl on accompanying Course + } + return $this->render('@ChillPerson/Household/members_editor.html.twig', [ - 'data' => $data + 'data' => $data, + 'expandSuggestions' => (int) $request->query->getBoolean('expand_suggestions', false), + 'accompanyingCourse' => $period ?? null, ]); } diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 733579acb..03b05bbc8 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -565,6 +565,13 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], 'controller_action' => 'householdAddressApi' ], + 'suggestHouseholdByAccompanyingPeriodParticipation' => [ + 'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}', + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ] + ] ] ], [ diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index c2a8299fc..b0578c994 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -25,8 +25,9 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\Persistence\ObjectRepository; -final class AccompanyingPeriodRepository +final class AccompanyingPeriodRepository implements ObjectRepository { private EntityRepository $repository; @@ -34,4 +35,32 @@ final class AccompanyingPeriodRepository { $this->repository = $entityManager->getRepository(AccompanyingPeriod::class); } + public function find($id): ?AccompanyingPeriod + { + return $this->repository->find($id); + } + + /** + * @return AccompanyingPeriod[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): ?AccompanyingPeriod + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?AccompanyingPeriod + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return AccompanyingPeriod::class; + } + } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php index b7a8816b3..d9ec6c35e 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php @@ -3,15 +3,102 @@ namespace Chill\PersonBundle\Repository\Household; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query\ResultSetMappingBuilder; +use Doctrine\Persistence\ObjectRepository; -final class HouseholdRepository +final class HouseholdRepository implements ObjectRepository { private EntityRepository $repository; + private EntityManagerInterface $em; public function __construct(EntityManagerInterface $entityManager) { $this->repository = $entityManager->getRepository(Household::class); + $this->em = $entityManager; } + + public function find($id) + { + return $this->repository->find($id); + } + + public function findAll() + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria) + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return Household::class; + } + + public function countByAccompanyingPeriodParticipation(Person $person) + { + return $this->buildQueryByAccompanyingPeriodParticipation($person, true); + } + + public function findByAccompanyingPeriodParticipation(Person $person, int $limit, int $offset) + { + return $this->buildQueryByAccompanyingPeriodParticipation($person, false, $limit, $offset); + } + + private function buildQueryByAccompanyingPeriodParticipation(Person $person, bool $isCount = false, int $limit = 50, int $offset = 0) + { + $rsm = new ResultSetMappingBuilder($this->em); + $rsm->addRootEntityFromClassMetadata(Household::class, 'h'); + + if ($isCount) { + $rsm->addScalarResult('count', 'count'); + $sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [ + '{select}' => 'COUNT(households.*) AS count', + '{limits}' => '' + ]); + } else { + $sql = \strtr(self::SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION, [ + '{select}' => $rsm->generateSelectClause(['h' => 'households']), + '{limits}' => "OFFSET {$offset} LIMIT {$limit}" + ]); + } + $native = $this->em->createNativeQuery($sql, $rsm); + $native->setParameters([0 => $person->getId(), 1 => $person->getId()]); + + if ($isCount) { + return $native->getSingleScalarResult(); + } else { + return $native->getResult(); + } + } + + private CONST SQL_BY_ACCOMPANYING_PERIOD_PARTICIPATION = << { }); }; +const fetchHouseholdSuggestionByAccompanyingPeriod = (personId) => { + const url = `/api/1.0/person/household/suggest/by-person/${personId}/through-accompanying-period-participation.json`; + return window.fetch(url) + .then(response => { + if (response.ok) { + return response.json(); + } + + throw Error ({m: 'Error while fetching household suggestion', status: response.status}); + }).then(data => Promise.resolve(data.results)) + .catch(e => console.err(e)); + ; +}; + export { householdMove, + fetchHouseholdSuggestionByAccompanyingPeriod, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue index d9312cf00..6901b3992 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue @@ -14,6 +14,23 @@ + +
+
+

{{ $t('household_members_editor.household_for_participants_accompanying_period') }}:

+
+
+ + +
    +
  • + +
  • +
+
+
+
+
+ + {{ encore_entry_script_tags('household_members_editor') }} {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php new file mode 100644 index 000000000..f6adc5d01 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php @@ -0,0 +1,55 @@ +getClientAuthenticated(); + + $client->request( + Request::METHOD_GET, + "/api/1.0/person/household/suggest/by-person/{$personId}/through-accompanying-period-participation.json" + ); + + $this->assertResponseIsSuccessful(); + } + + public function generatePersonId() + { + self::bootKernel(); + + $qb = self::$container->get(EntityManagerInterface::class) + ->createQueryBuilder(); + + $period = $qb + ->select('ap') + ->from(AccompanyingPeriod::class, 'ap') + ->where( + $qb->expr()->gte('SIZE(ap.participations)', 2) + ) + ->getQuery() + ->setMaxResults(1) + ->getSingleResult() + ; + + $person = $period->getParticipations() + ->first()->getPerson(); + + yield [ $person->getId() ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml index 6c7f1ef62..3bfd1fb57 100644 --- a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml @@ -967,6 +967,36 @@ paths: 401: description: "Unauthorized" + /1.0/person/household/suggest/by-person/{person_id}/through-accompanying-period-participation.json: + get: + tags: + - household + summary: Return households associated with the given person through accompanying periods + description: | + Return households associated with the given person throught accompanying periods participation. + + The current household of the given person is excluded. + parameters: + - name: person_id + in: path + required: true + description: The person's id + schema: + type: integer + format: integer + minimum: 1 + responses: + 200: + description: "ok" + content: + application/json: + schema: + $ref: '#/components/schemas/Household' + 404: + description: "not found" + 401: + description: "Unauthorized" + /1.0/person/household/members/move.json: post: tags: