Link between address and reference, and api endpoint to find household

by address reference

* add a one-to-many link between address and address reference;
* update AddAddress.vue to add information on picked address reference;
* add an HouseholdACLAwareRepository, with a method to find household by
current address reference
* add an endpoint to retrieve household by address reference
* + tests
This commit is contained in:
2021-09-29 23:55:54 +02:00
parent 6430de94a7
commit 05b2b2f9b8
12 changed files with 365 additions and 21 deletions

View File

@@ -4,24 +4,31 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface;
use Chill\PersonBundle\Repository\Household\HouseholdRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
class HouseholdApiController extends ApiController
{
private HouseholdRepository $householdRepository;
public function __construct(HouseholdRepository $householdRepository)
{
private HouseholdACLAwareRepositoryInterface $householdACLAwareRepository;
public function __construct(
HouseholdRepository $householdRepository,
HouseholdACLAwareRepositoryInterface $householdACLAwareRepository
) {
$this->householdRepository = $householdRepository;
$this->householdACLAwareRepository = $householdACLAwareRepository;
}
public function householdAddressApi($id, Request $request, string $_format): Response
{
@@ -37,7 +44,7 @@ class HouseholdApiController extends ApiController
{
// TODO add acl
$count = $this->householdRepository->countByAccompanyingPeriodParticipation($person);
$count = $this->householdRepository->countByAccompanyingPeriodParticipation($person);
$paginator = $this->getPaginatorFactory()->create($count);
if ($count === 0) {
@@ -93,4 +100,27 @@ class HouseholdApiController extends ApiController
return $this->json(\array_values($addresses), Response::HTTP_OK, [],
[ 'groups' => [ 'read' ] ]);
}
/**
*
* @Route("/api/1.0/person/household/by-address-reference/{id}.json",
* name="chill_api_person_household_by_address_reference")
* @param AddressReference $addressReference
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function getHouseholdByAddressReference(AddressReference $addressReference): Response
{
// TODO ACL
$this->denyAccessUnlessGranted('ROLE_USER');
$total = $this->householdACLAwareRepository->countByAddressReference($addressReference);
$paginator = $this->getPaginatorFactory()->create($total);
$households = $this->householdACLAwareRepository->findByAddressReference($addressReference,
$paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());
$collection = new Collection($households, $paginator);
return $this->json($collection, Response::HTTP_OK, [], [
AbstractNormalizer::GROUPS => ['read']
]);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Chill\PersonBundle\Repository\Household;
use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
final class HouseholdACLAwareRepository implements HouseholdACLAwareRepositoryInterface
{
private EntityManagerInterface $em;
private AuthorizationHelper $authorizationHelper;
private Security $security;
public function __construct(EntityManagerInterface $em, AuthorizationHelper $authorizationHelper, Security $security)
{
$this->em = $em;
$this->authorizationHelper = $authorizationHelper;
$this->security = $security;
}
public function countByAddressReference(AddressReference $addressReference): int
{
$qb = $this->buildQueryByAddressReference($addressReference);
$qb = $this->addACL($qb);
return $qb->select('COUNT(h)')
->getQuery()
->getSingleScalarResult();
}
public function findByAddressReference(
AddressReference $addressReference,
?int $firstResult = 0,
?int $maxResult = 50
): array {
$qb = $this->buildQueryByAddressReference($addressReference);
$qb = $this->addACL($qb);
return $qb
->select('h')
->setFirstResult($firstResult)
->setMaxResults($maxResult)
->getQuery()
->getResult();
}
public function buildQueryByAddressReference(AddressReference $addressReference): QueryBuilder
{
$qb = $this->em->createQueryBuilder();
$qb
->select('h')
->from(Household::class, 'h')
->join('h.addresses', 'address')
->where(
$qb->expr()->eq('address.addressReference', ':reference')
)
->setParameter(':reference', $addressReference)
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte('address.validFrom', ':today'),
$qb->expr()->orX(
$qb->expr()->isNull('address.validTo'),
$qb->expr()->gt('address.validTo', ':today')
)
)
)
->setParameter('today', new \DateTime('today'))
;
return $qb;
}
public function addACL(QueryBuilder $qb, string $alias = 'h'): QueryBuilder
{
$centers = $this->authorizationHelper->getReachableCenters(
$this->security->getUser(),
HouseholdVoter::SHOW
);
if ([] === $centers) {
return $qb
->andWhere("'FALSE' = 'TRUE'");
}
$qb
->join($alias.'.members', 'members')
->join('members.person', 'person')
->andWhere(
$qb->expr()->in('person.center', ':centers')
)
->setParameter('centers', $centers);
return $qb;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Chill\PersonBundle\Repository\Household;
use Chill\MainBundle\Entity\AddressReference;
use Chill\PersonBundle\Entity\Household\Household;
interface HouseholdACLAwareRepositoryInterface
{
public function countByAddressReference(AddressReference $addressReference): int;
/**
* @param AddressReference $addressReference
* @param int|null $firstResult
* @param int|null $maxResult
* @return array|Household[]
*/
public function findByAddressReference(
AddressReference $addressReference,
?int $firstResult = 0,
?int $maxResult = 50
): array;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Chill\PersonBundle\Security\Authorization;
class HouseholdVoter
{
const SHOW = PersonVoter::SEE;
}

View File

@@ -2,9 +2,14 @@
namespace Chill\PersonBundle\Tests\Controller;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Entity\Center;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
@@ -15,6 +20,8 @@ class HouseholdApiControllerTest extends WebTestCase
use PrepareClientTrait;
private array $toDelete = [];
/**
* @dataProvider generatePersonId
*/
@@ -45,6 +52,77 @@ class HouseholdApiControllerTest extends WebTestCase
$this->assertResponseIsSuccessful();
}
/**
* @dataProvider generateHouseholdAssociatedWithAddressReference
*/
public function testFindHouseholdByAddressReference(int $addressReferenceId, int $expectedHouseholdId)
{
$client = $this->getClientAuthenticated();
$client->request(
Request::METHOD_GET,
"/api/1.0/person/household/by-address-reference/$addressReferenceId.json"
);
$this->assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertArrayHasKey('count', $data);
$this->assertArrayHasKey('results', $data);
$householdIds = \array_map(function($r) {
return $r['id'];
}, $data['results']);
$this->assertContains($expectedHouseholdId, $householdIds);
}
public function generateHouseholdAssociatedWithAddressReference()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$centerA = $em->getRepository(Center::class)->findOneBy(['name' => 'Center A']);
$nbReference = $em->createQueryBuilder()->select('count(ar)')->from(AddressReference::class, 'ar')
->getQuery()->getSingleScalarResult();
$reference = $em->createQueryBuilder()->select('ar')->from(AddressReference::class, 'ar')
->setFirstResult(\random_int(0, $nbReference))
->setMaxResults(1)
->getQuery()->getSingleResult();
$p = new Person();
$p->setFirstname('test')->setLastName('test lastname')
->setGender(Person::BOTH_GENDER)
->setCenter($centerA)
;
$em->persist($p);
$h = new Household();
$h->addMember($m = (new HouseholdMember())->setPerson($p));
$h->addAddress(Address::createFromAddressReference($reference)->setValidFrom(new \DateTime('today')));
$em->persist($m);
$em->persist($h);
$em->flush();
$this->toDelete = $this->toDelete + [
[HouseholdMember::class, $m->getId()],
[User::class, $p->getId()],
[Household::class, $h->getId()]
];
yield [$reference->getId(), $h->getId()];
}
protected function tearDown()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
foreach ($this->toDelete as list($class, $id)) {
$obj = $em->getRepository($class)->find($id);
$em->remove($obj);
}
$em->flush();
}
public function generatePersonId()
{
self::bootKernel();
@@ -64,7 +142,7 @@ class HouseholdApiControllerTest extends WebTestCase
;
$person = $period->getParticipations()
->first()->getPerson();
->first()->getPerson();
yield [ $person->getId() ];
}

View File

@@ -1127,6 +1127,32 @@ paths:
401:
description: "Unauthorized"
/1.0/person/household/by-address-reference/{address_id}.json:
get:
tags:
- household
summary: Return a list of household which are sharing the same address reference
parameters:
- name: address_id
in: path
required: true
description: the address reference 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/suggest/by-person/{person_id}/through-accompanying-period-participation.json:
get:
tags:

View File

@@ -10,3 +10,5 @@ services:
Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface: '@Chill\PersonBundle\Repository\PersonACLAwareRepository'
Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface: '@Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository'
Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface: '@Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepository'