backend: allow to remove people from household, or move to a new household

This commit is contained in:
Julien Fastré 2021-06-10 13:01:42 +02:00
parent 6fed008ff2
commit 89734c680c
6 changed files with 261 additions and 18 deletions

View File

@ -55,7 +55,11 @@ class HouseholdMemberController extends ApiController
$em = $this->getDoctrine()->getManager();
// if new household, persist it
if (FALSE === $em->contains($editor->getHousehold())) {
if (
$editor->hasHousehold()
&&
FALSE === $em->contains($editor->getHousehold())
) {
$em->persist($editor->getHousehold());
}

View File

@ -3,6 +3,7 @@
namespace Chill\PersonBundle\Household;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Doctrine\Common\Collections\Criteria;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Entity\Household\Household;
@ -13,12 +14,12 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
class MembersEditor
{
private ValidatorInterface $validator;
private Household $household;
private ?Household $household = null;
private array $persistables = [];
private array $membershipsAffected = [];
public function __construct(ValidatorInterface $validator, Household $household)
public function __construct(ValidatorInterface $validator, ?Household $household)
{
$this->validation = $validator;
$this->household = $household;
@ -62,6 +63,33 @@ class MembersEditor
return $this;
}
public function leaveMovement(
\DateTimeImmutable $date,
Person $person
): self {
$criteria = new Criteria();
$expr = Criteria::expr();
$criteria->where(
$expr->andX(
$expr->lt('startDate', $date),
$expr->isNull('endDate', $date)
)
);
$participations = $person->getHouseholdParticipations()
->matching($criteria)
;
foreach ($participations as $participation) {
$participation->setEndDate($date);
$this->membershipsAffected[] = $participation;
}
return $this;
}
public function validate(): ConstraintViolationListInterface
{
@ -72,8 +100,13 @@ class MembersEditor
return $this->persistables;
}
public function getHousehold(): Household
public function getHousehold(): ?Household
{
return $this->household;
}
public function hasHousehold(): bool
{
return $this->household !== null;
}
}

View File

@ -14,7 +14,7 @@ class MembersEditorFactory
$this->validator = $validator;
}
public function createEditor(Household $household): MembersEditor
public function createEditor(?Household $household = null): MembersEditor
{
return new MembersEditor($this->validator, $household);
}

View File

@ -24,6 +24,58 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
// some test about schema first...
$this->performChecks($data);
// route to "leave movement" (all concerned leave household)
// or "move to another household" (all concerned go to another
// household)
if (NULL === $data['destination']) {
return $this->denormalizeLeave($data, $type, $format, $context);
} else {
return $this->denormalizeMove($data, $type, $format, $context);
}
}
private function performChecks($data): void
{
if (NULL == $data['concerned'] ?? NULL
&& FALSE === ·\is_array('concerned')) {
throw new Exception\UnexpectedValueException("The schema does not have any key 'concerned'");
}
if (FALSE === \array_key_exists('destination', $data)) {
throw new Exception\UnexpectedValueException("The schema does not have any key 'destination'");
}
}
protected function denormalizeLeave($data, string $type, string $format, array $context = [])
{
$editor = $this->factory->createEditor(null);
foreach ($data['concerned'] as $key => $concerned) {
$person = $this->denormalizer->denormalize($concerned['person'] ?? null, Person::class,
$format, $context);
$startDate = $this->denormalizer->denormalize($concerned['start_date'] ?? null, \DateTimeImmutable::class,
$format, $context);
if (
NULL === $person
&& NULL === $startDate
) {
throw new Exception\InvalidArgumentException("position with ".
"key $key could not be denormalized: missing ".
"person or start_date.");
}
$editor->leaveMovement($startDate, $person);
}
return $editor;
}
protected function denormalizeMove($data, string $type, string $format, array $context = [])
{
$household = $this->denormalizer->denormalize($data['destination'], Household::class,
$format, $context);
@ -34,11 +86,6 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
$editor = $this->factory->createEditor($household);
if (NULL == $data['concerned'] ?? NULL
&& FALSE === ·\is_array('concerned')) {
throw new Exception\UnexpectedValueException("The schema does not have any key 'concerned'");
}
foreach ($data['concerned'] as $key => $concerned) {
$person = $this->denormalizer->denormalize($concerned['person'] ?? null, Person::class,
$format, $context);
@ -62,9 +109,9 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
$editor->addMovement($startDate, $person, $position, $holder,
$comment);
return $editor;
}
return $editor;
}
public function supportsDenormalization($data, string $type, string $format = null)

View File

@ -67,6 +67,116 @@ class HouseholdMemberControllerTest extends WebTestCase
);
}
/**
* @dataProvider provideValidDataMove
*/
public function testMoveMemberToNewHousehold($personId, $householdId, $positionId, \DateTimeInterface $date)
{
$client = $this->getClientAuthenticated();
$client->request(
Request::METHOD_POST,
'/api/1.0/person/household/members/move.json',
[], // parameters
[], // files
[], // server
\json_encode(
[
'concerned' =>
[
[
'person' =>
[
'type' => 'person',
'id' => $personId
],
'start_date' =>
[
'datetime' => $date->format(\DateTimeInterface::RFC3339)
],
'position' =>
[
'type' => 'household_position',
'id' => $positionId
],
'holder' => false,
'comment' => "Introduced by automated test",
],
],
'destination' =>
[
'type' => 'household',
]
],
true)
);
$this->assertEquals(Response::HTTP_OK,
$client->getResponse()->getStatusCode()
);
$data = \json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('members', $data);
$this->assertIsArray($data['members']);
$this->assertEquals(1, count($data['members']),
"assert new household count one member");
$this->assertArrayHasKey('person', $data['members'][0]);
$this->assertArrayHasKey('id', $data['members'][0]['person']);
$this->assertEquals($personId, $data['members'][0]['person']['id']);
}
/**
* @dataProvider provideValidDataMove
*/
public function testLeaveWithoutHousehold($personId, $householdId, $positionId, \DateTimeInterface $date)
{
$client = $this->getClientAuthenticated();
$client->request(
Request::METHOD_POST,
'/api/1.0/person/household/members/move.json',
[], // parameters
[], // files
[], // server
\json_encode(
[
'concerned' =>
[
[
'person' =>
[
'type' => 'person',
'id' => $personId
],
'start_date' =>
[
'datetime' => $date->format(\DateTimeInterface::RFC3339)
],
'position' =>
[
'type' => 'household_position',
'id' => $positionId
],
'holder' => false,
'comment' => "Introduced by automated test",
],
],
'destination' => null
],
true)
);
$this->assertEquals(Response::HTTP_OK,
$client->getResponse()->getStatusCode()
);
$data = \json_decode($client->getResponse()->getContent(), true);
$this->assertEquals(null, $data);
}
/**
* @dataProvider provideValidDataEditMember
*/
@ -97,7 +207,13 @@ class HouseholdMemberControllerTest extends WebTestCase
$em = self::$container->get(EntityManagerInterface::class);
$personIds = $em->createQuery("SELECT p.id FROM ".Person::class." p ".
"JOIN p.center c WHERE c.name = :center")
"JOIN p.center c ".
"JOIN p.householdParticipations hp ".
"WHERE ".
"c.name = :center ".
"AND hp.startDate < CURRENT_DATE() ".
"AND hp.endDate IS NULL "
)
->setParameter('center', "Center A")
->setMaxResults(100)
->getScalarResult()

View File

@ -802,18 +802,61 @@ paths:
type: object
properties:
person:
$ref: '#/components/schemas/PersonById'
$ref: '#/components/schemas/PersonById'
start_date:
$ref: '#/components/schemas/Date'
$ref: '#/components/schemas/Date'
position:
$ref: '#/components/schemas/HouseholdPosition'
$ref: '#/components/schemas/HouseholdPosition'
holder:
type: boolean
comment:
type: string
destination:
oneOf:
- $ref: '#/components/schemas/Household'
$ref: '#/components/schemas/Household'
examples:
Moving person to a new household:
value:
concerned:
-
person:
id: 0
type: person
position:
type: position
id: 1
start_date:
datetime: 2021-06-01T00:00:00+02:00
comment: "This is my comment for moving"
holder: false
destination:
type: household
Moving person to an existing household:
value:
concerned:
-
person:
id: 0
type: person
position:
type: position
id: 1
start_date:
datetime: 2021-06-01T00:00:00+02:00
comment: "This is my comment for moving"
holder: false
destination:
type: household
id: 54
Removing a person from any household:
value:
concerned:
-
person:
id: 0
type: person
start_date:
datetime: 2021-06-01T00:00:00+02:00
destination: null
responses:
401:
description: "Unauthorized"