mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Controller action to move members of an household
This commit is contained in:
parent
ace3b1969e
commit
041b1dfc51
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Serializer\Exception;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\PersonBundle\Household\MembersEditor;
|
||||
|
||||
class HouseholdMemberController extends ApiController
|
||||
{
|
||||
/**
|
||||
* @Route(
|
||||
* "/api/1.0/person/household/members/move.{_format}",
|
||||
* name="chill_person_household_members_move"
|
||||
* )
|
||||
*/
|
||||
public function move(Request $request, $_format): Response
|
||||
{
|
||||
try {
|
||||
$editor = $this->getSerializer()
|
||||
->deserialize($request->getContent(), MembersEditor::class,
|
||||
$_format, ['groups' => [ "read" ]]);
|
||||
} catch (Exception\InvalidArgumentException | Exception\UnexpectedValueException $e) {
|
||||
throw new BadRequestException("Deserialization error: {$e->getMessage()}", 45896, $e);
|
||||
}
|
||||
dump($editor);
|
||||
// TODO ACL
|
||||
//
|
||||
// TODO validation
|
||||
//
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
// to ensure closing membership before creating one, we must manually open a transaction
|
||||
$em->beginTransaction();
|
||||
|
||||
foreach ($editor->getPersistable() as $el) {
|
||||
$em->persist($el);
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
|
||||
|
||||
return $this->json($editor->getHousehold(), Response::HTTP_OK, ["groups" => ["read"]]);
|
||||
}
|
||||
}
|
@ -5,12 +5,16 @@ namespace Chill\PersonBundle\Entity\Household;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(
|
||||
* name="chill_person_household"
|
||||
* )
|
||||
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "household"=Household::class
|
||||
* })
|
||||
*/
|
||||
class Household
|
||||
{
|
||||
@ -18,6 +22,7 @@ class Household
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private $id;
|
||||
|
||||
|
@ -29,12 +29,12 @@ class HouseholdMember
|
||||
private ?Position $position = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="date", nullable=true, options={"default": null})
|
||||
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||
*/
|
||||
private ?\DateTimeImmutable $startDate = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="date", nullable= true, options={"default": null})
|
||||
* @ORM\Column(type="date_immutable", nullable= true, options={"default": null})
|
||||
*/
|
||||
private ?\DateTimeImmutable $endDate = null;
|
||||
|
||||
@ -95,24 +95,24 @@ class HouseholdMember
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?\DateTimeInterface
|
||||
public function getStartDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function setStartDate(\DateTimeInterface $startDate): self
|
||||
public function setStartDate(\DateTimeImmutable $startDate): self
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeInterface
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function setEndDate(\DateTimeInterface $endDate): self
|
||||
public function setEndDate(\DateTimeImmutable $endDate): self
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
|
||||
@ -150,8 +150,7 @@ class HouseholdMember
|
||||
}
|
||||
|
||||
$this->person = $person;
|
||||
|
||||
$person->addHouseholdParticipation($this);
|
||||
$this->person->addHouseholdParticipation($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -4,10 +4,14 @@ namespace Chill\PersonBundle\Entity\Household;
|
||||
|
||||
use Chill\PersonBundle\Repository\Household\PositionRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=PositionRepository::class)
|
||||
* @ORM\Table(name="chill_person_household_position")
|
||||
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "household_position"=Position::class
|
||||
* })
|
||||
*/
|
||||
class Position
|
||||
{
|
||||
@ -15,6 +19,7 @@ class Position
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({ "read" })
|
||||
*/
|
||||
private $id;
|
||||
|
||||
|
@ -16,7 +16,7 @@ class MembersEditor
|
||||
private Household $household;
|
||||
|
||||
private array $persistables = [];
|
||||
private array $memershipsAffected = [];
|
||||
private array $membershipsAffected = [];
|
||||
|
||||
public function __construct(ValidatorInterface $validator, Household $household)
|
||||
{
|
||||
@ -24,7 +24,7 @@ class MembersEditor
|
||||
$this->household = $household;
|
||||
}
|
||||
|
||||
public function addMovement(\DateTimeInterface $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self
|
||||
public function addMovement(\DateTimeImmutable $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");
|
||||
@ -71,4 +71,9 @@ class MembersEditor
|
||||
{
|
||||
return $this->persistables;
|
||||
}
|
||||
|
||||
public function getHousehold(): Household
|
||||
{
|
||||
return $this->household;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Chill\PersonBundle\Repository\Household;
|
||||
|
||||
use App\Entity\Chill\PersonBundle\Entity\Household\Position;
|
||||
use Chill\PersonBundle\Entity\Household\Position;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
@ -19,32 +19,4 @@ class PositionRepository extends ServiceEntityRepository
|
||||
parent::__construct($registry, Position::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Position[] Returns an array of Position objects
|
||||
// */
|
||||
/*
|
||||
public function findByExampleField($value)
|
||||
{
|
||||
return $this->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()
|
||||
;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\PersonBundle\Entity\Household\Position;
|
||||
use Chill\PersonBundle\Entity\Household\Household;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Exception;
|
||||
use Chill\PersonBundle\Household\MembersEditorFactory;
|
||||
use Chill\PersonBundle\Household\MembersEditor;
|
||||
|
||||
class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwareInterface
|
||||
{
|
||||
private MembersEditorFactory $factory;
|
||||
|
||||
use DenormalizerAwareTrait;
|
||||
|
||||
public function __construct(MembersEditorFactory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
public function denormalize($data, string $type, string $format = null, array $context = [])
|
||||
{
|
||||
$household = $this->denormalizer->denormalize($data['destination'], Household::class,
|
||||
$format, $context);
|
||||
|
||||
if (NULL === $household) {
|
||||
throw new Exception\InvalidArgumentException("household could not be denormalized. Impossible to process");
|
||||
}
|
||||
|
||||
$editor = $this->factory->createEditor($household);
|
||||
|
||||
if (NULL == $data['concerned'] ?? []
|
||||
&& 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);
|
||||
$position = $this->denormalizer->denormalize($concerned['position'] ?? null, Position::class,
|
||||
$format, $context);
|
||||
$startDate = $this->denormalizer->denormalize($concerned['start_date'] ?? null, \DateTimeImmutable::class,
|
||||
$format, $context);
|
||||
|
||||
$holder = (bool) $concerned['holder'] ?? false;
|
||||
$comment = (string) $concerned['comment'] ?? false;
|
||||
|
||||
if (
|
||||
NULL === $person
|
||||
&& NULL === $position
|
||||
&& NULL === $startDate
|
||||
) {
|
||||
throw new Exception\InvalidArgumentException("position with ".
|
||||
"key $key could not be denormalized: missing ".
|
||||
"person, position or start_date.");
|
||||
}
|
||||
|
||||
$editor->addMovement($startDate, $person, $position, $holder,
|
||||
$comment);
|
||||
|
||||
return $editor;
|
||||
}
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, string $format = null)
|
||||
{
|
||||
return $type === MembersEditor::class;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Bundle\ChillPersonBundle\Tests\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\Household\Household;
|
||||
use Chill\PersonBundle\Entity\Household\Position;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
|
||||
class HouseholdMemberControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider provideValidData
|
||||
*/
|
||||
public function testMoveMember($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',
|
||||
'id' => $householdId
|
||||
]
|
||||
],
|
||||
true)
|
||||
);
|
||||
|
||||
$this->assertEquals(Response::HTTP_OK,
|
||||
$client->getResponse()->getStatusCode()
|
||||
);
|
||||
}
|
||||
|
||||
public function provideValidData(): \Iterator
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::$container->get(EntityManagerInterface::class);
|
||||
|
||||
$personIds = $em->createQuery("SELECT p.id FROM ".Person::class." p ".
|
||||
"JOIN p.center c WHERE c.name = :center")
|
||||
->setParameter('center', "Center A")
|
||||
->setMaxResults(100)
|
||||
->getScalarResult()
|
||||
;
|
||||
\shuffle($personIds);
|
||||
|
||||
$household = new Household();
|
||||
$em->persist($household);
|
||||
$em->flush();
|
||||
|
||||
$positions = $em->createQuery("SELECT pos.id FROM ".Position::class." pos ".
|
||||
"WHERE pos.shareHouseHold = TRUE")
|
||||
->getResult()
|
||||
;
|
||||
|
||||
yield [
|
||||
\array_pop($personIds)['id'],
|
||||
$household->getId(),
|
||||
$positions[\random_int(0, count($positions) - 1)]['id'],
|
||||
new \DateTimeImmutable('today')
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Bundle\ChillPersonBundle\Tests\Entity\Household;
|
||||
|
||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||
use Chill\PersonBundle\Entity\Household\Position;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HouseholdMemberTest extends TestCase
|
||||
{
|
||||
public function testPositionSharehousehold()
|
||||
{
|
||||
$position = (new Position())
|
||||
->setShareHousehold(true)
|
||||
;
|
||||
$membership = (new HouseholdMember())
|
||||
->setPosition($position)
|
||||
;
|
||||
|
||||
$this->assertTrue($membership->getShareHousehold());
|
||||
}
|
||||
|
||||
public function testPositionDoNotSharehousehold()
|
||||
{
|
||||
$position = (new Position())
|
||||
->setShareHousehold(false)
|
||||
;
|
||||
$membership = (new HouseholdMember())
|
||||
->setPosition($position)
|
||||
;
|
||||
|
||||
$this->assertFalse($membership->getShareHousehold());
|
||||
}
|
||||
}
|
@ -37,10 +37,10 @@ class MembersEditorTest extends TestCase
|
||||
$person,
|
||||
$position);
|
||||
|
||||
$this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations());
|
||||
$this->assertEquals(1, $person->getHouseholdParticipations()->count());
|
||||
$persistables = $editor->getPersistable();
|
||||
$this->assertEquals(\count($persistables), 1);
|
||||
|
||||
$membership1 = $person->getHouseholdParticipations()->first();
|
||||
$membership1 = $persistables[0];
|
||||
$this->assertSame($household1, $membership1->getHousehold());
|
||||
$this->assertNull($membership1->getEndDate());
|
||||
|
||||
@ -52,9 +52,10 @@ class MembersEditorTest extends TestCase
|
||||
$person,
|
||||
$position);
|
||||
|
||||
$this->assertEquals(2, $person->getHouseholdParticipations()->count());
|
||||
$persistables = $editor->getPersistable();
|
||||
$this->assertEquals(1, count($persistables));
|
||||
|
||||
$membership2 = $person->getHouseholdParticipations()->last();
|
||||
$membership2 = $persistables[0];
|
||||
$this->assertSame($household2, $membership2->getHousehold());
|
||||
$this->assertNull($membership2->getEndDate());
|
||||
$this->assertNotNull($membership1->getEndDate(),
|
||||
@ -77,8 +78,8 @@ class MembersEditorTest extends TestCase
|
||||
$person,
|
||||
$position);
|
||||
|
||||
$this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations());
|
||||
$this->assertEquals(1, $person->getHouseholdParticipations()->count());
|
||||
$persistables = $editor->getPersistable();
|
||||
$this->assertEquals(1, count($persistables));
|
||||
|
||||
$membership1 = $person->getHouseholdParticipations()->first();
|
||||
$this->assertSame($household1, $membership1->getHousehold());
|
||||
@ -92,7 +93,8 @@ class MembersEditorTest extends TestCase
|
||||
$person,
|
||||
$position);
|
||||
|
||||
$this->assertEquals(2, $person->getHouseholdParticipations()->count());
|
||||
$persistables = $editor->getPersistable();
|
||||
$this->assertEquals(1, count($persistables));
|
||||
|
||||
$membership2 = $person->getHouseholdParticipations()->last();
|
||||
$this->assertNull($membership2->getEndDate());
|
||||
|
@ -192,6 +192,25 @@ components:
|
||||
text:
|
||||
type: string
|
||||
readOnly: true
|
||||
Household:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'household'
|
||||
HouseholdPosition:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'household_position'
|
||||
|
||||
|
||||
paths:
|
||||
/1.0/person/person/{id}.json:
|
||||
@ -764,3 +783,46 @@ paths:
|
||||
description: "OK"
|
||||
400:
|
||||
description: "transition cannot be applyed"
|
||||
|
||||
/1.0/person/household/members/move.json:
|
||||
post:
|
||||
tags:
|
||||
- household
|
||||
summary: move one or multiple person from a household to another
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
concerned:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
person:
|
||||
$ref: '#/components/schemas/PersonById'
|
||||
start_date:
|
||||
$ref: '#/components/schemas/Date'
|
||||
position:
|
||||
$ref: '#/components/schemas/HouseholdPosition'
|
||||
holder:
|
||||
type: boolean
|
||||
comment:
|
||||
type: string
|
||||
destination:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Household'
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
404:
|
||||
description: "Not found"
|
||||
200:
|
||||
description: "OK"
|
||||
422:
|
||||
description: "Unprocessable entity (validation errors)"
|
||||
400:
|
||||
description: "transition cannot be applyed"
|
||||
|
||||
|
@ -42,7 +42,8 @@ final class Version20210528092625 extends AbstractMigration
|
||||
-- extension btree_gist required to include comparaison with integer
|
||||
person_id WITH =,
|
||||
daterange(startdate, enddate) WITH &&
|
||||
) WHERE (sharedhousehold IS TRUE)");
|
||||
) WHERE (sharedhousehold IS TRUE)
|
||||
INITIALLY DEFERRED");
|
||||
|
||||
// rename constraints
|
||||
$this->addSql('ALTER TABLE public.chill_person_household_to_addresses DROP CONSTRAINT fk_7109483e79ff843');
|
||||
|
Loading…
x
Reference in New Issue
Block a user