PersonAddressMoveEvent on household move (wip)

This commit is contained in:
Julien Fastré 2022-02-19 14:04:51 +01:00
parent 8675bb65c1
commit caa63ea97a
11 changed files with 237 additions and 48 deletions

View File

@ -918,15 +918,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php path: src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodController.php
-
message:
"""
#^Parameter \\$translator of method Chill\\\\PersonBundle\\\\Controller\\\\HouseholdController\\:\\:__construct\\(\\) has typehint with deprecated interface Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\:
since Symfony 4\\.2, use Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface instead$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Controller/HouseholdController.php
- -
message: message:
""" """

View File

@ -142,7 +142,7 @@ class Address
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
* @Groups({"write"}) * @Groups({"write"})
*/ */
private ?PostalCode $postcode; private ?PostalCode $postcode = null;
/** /**
* @var string|null * @var string|null
@ -305,9 +305,8 @@ class Address
/** /**
* Get postcode. * Get postcode.
* *
* @return PostalCode
*/ */
public function getPostcode() public function getPostcode(): ?PostalCode
{ {
return $this->postcode; return $this->postcode;
} }

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\AccompanyingPeriod\Events; namespace Chill\PersonBundle\AccompanyingPeriod\Events;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
@ -78,7 +79,7 @@ class PersonMoveEventSubscriber implements EventSubscriberInterface
) { ) {
// reset the location, back to an address // reset the location, back to an address
$period->setPersonLocation(null); $period->setPersonLocation(null);
$period->setAddressLocation($event->getPreviousAddress()); $period->setAddressLocation(Address::createFromAddress($event->getPreviousAddress()));
$notification = new Notification(); $notification = new Notification();
$notification $notification

View File

@ -16,15 +16,20 @@ use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference; use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface; use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface;
use Chill\PersonBundle\Repository\Household\HouseholdRepository; use Chill\PersonBundle\Repository\Household\HouseholdRepository;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use function array_filter; use function array_filter;
use function array_values; use function array_values;
@ -34,10 +39,14 @@ class HouseholdApiController extends ApiController
private HouseholdRepository $householdRepository; private HouseholdRepository $householdRepository;
private EventDispatcherInterface $eventDispatcher;
public function __construct( public function __construct(
EventDispatcherInterface $eventDispatcher,
HouseholdRepository $householdRepository, HouseholdRepository $householdRepository,
HouseholdACLAwareRepositoryInterface $householdACLAwareRepository HouseholdACLAwareRepositoryInterface $householdACLAwareRepository
) { ) {
$this->eventDispatcher = $eventDispatcher;
$this->householdRepository = $householdRepository; $this->householdRepository = $householdRepository;
$this->householdACLAwareRepository = $householdACLAwareRepository; $this->householdACLAwareRepository = $householdACLAwareRepository;
} }
@ -66,9 +75,51 @@ class HouseholdApiController extends ApiController
]); ]);
} }
public function householdAddressApi($id, Request $request, string $_format): Response /**
* Add an address to a household
*
* @Route("/api/1.0/person/household/{id}/address.{_format}", name="chill_api_single_household_address",
* methods={"POST"}, requirements={"_format": "json"})
*/
public function householdAddressApi(Household $household, Request $request, string $_format): Response
{ {
return $this->addRemoveSomething('address', $id, $request, $_format, 'address', Address::class, ['groups' => ['read']]); $this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household);
/** @var Address $address */
$address = $this->getSerializer()->deserialize($request->getContent(), Address::class, $_format, [
AbstractNormalizer::GROUPS => ['write']
]);
$household->addAddress($address);
foreach ($household->getMembersOnRange(
\DateTimeImmutable::createFromMutable($address->getValidFrom()),
null === $address->getValidTo() ? null :
\DateTimeImmutable::createFromMutable($address->getValidTo())
) as $member) {
/** @var HouseholdMember $member */
$event = new PersonAddressMoveEvent($member->getPerson());
$event
->setPreviousAddress($household->getPreviousAddressOf($address))
->setNextAddress($address)
;
$this->eventDispatcher->dispatch($event);
}
$errors = $this->getValidator()->validate($household);
if ($errors->count() > 0) {
return $this->json($errors, 422);
}
$this->getDoctrine()->getManager()->flush();
return $this->json(
$address,
Response::HTTP_OK,
[],
[AbstractNormalizer::GROUPS => ["read"]]
);
} }
/** /**

View File

@ -24,7 +24,7 @@ use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists; use function array_key_exists;
use function count; use function count;

View File

@ -527,15 +527,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
Request::METHOD_HEAD => true, Request::METHOD_HEAD => true,
], ],
], ],
'address' => [
'methods' => [
Request::METHOD_POST => true,
Request::METHOD_DELETE => true,
Request::METHOD_GET => false,
Request::METHOD_HEAD => false,
],
'controller_action' => 'householdAddressApi',
],
'suggestHouseholdByAccompanyingPeriodParticipation' => [ 'suggestHouseholdByAccompanyingPeriodParticipation' => [
'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}', 'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}',
'methods' => [ 'methods' => [

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Entity\AccompanyingPeriod; namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -22,7 +23,7 @@ use Doctrine\ORM\Mapping as ORM;
* @ORM\Entity * @ORM\Entity
* @ORM\Table("chill_person_accompanying_period_location_history") * @ORM\Table("chill_person_accompanying_period_location_history")
*/ */
class AccompanyingPeriodLocationHistory class AccompanyingPeriodLocationHistory implements TrackCreationInterface
{ {
use TrackCreationTrait; use TrackCreationTrait;

View File

@ -106,20 +106,74 @@ class Household
$this->compositions = new ArrayCollection(); $this->compositions = new ArrayCollection();
} }
/** public function addAddress(Address $address): self
* @return $this
*/
public function addAddress(Address $address)
{ {
foreach ($this->getAddresses() as $a) { if (!$this->addresses->contains($address)) {
if ($a->getValidFrom() <= $address->getValidFrom() && $a->getValidTo() === null) { $this->addresses[] = $address;
$a->setValidTo($address->getValidFrom()); $this->makeAddressConsistent();
}
return $this;
}
public function getAddressesOrdered(): array
{
$addresses = $this->getAddresses()->toArray();
usort($addresses, function (Address $a, Address $b) {
$validFromA = $a->getValidFrom()->format('Y-m-d');
$validFromB = $b->getValidFrom()->format('Y-m-d');
if ($a === $b) {
if (null === $a->getId()) {
return 1;
}
if (null === $b->getId()) {
return -1;
}
return $a->getId() <=> $b->getId();
}
return $validFromA <=> $validFromB;
});
return $addresses;
}
public function getPreviousAddressOf(Address $address): ?Address
{
$iterator = new ArrayIterator($this->getAddressesOrdered());
$iterator->rewind();
while ($iterator->valid()) {
$current = $iterator->current();
$iterator->next();
if ($iterator->valid()) {
if ($address === $iterator->current()) {
return $current;
}
} }
} }
$this->addresses[] = $address; return null;
}
return $this; public function makeAddressConsistent(): void
{
$iterator = new ArrayIterator($this->getAddressesOrdered());
$iterator->rewind();
while ($iterator->valid()) {
$current = $iterator->current();
$iterator->next();
if ($iterator->valid()) {
$current->setValidTo($iterator->current()->getValidFrom());
}
}
} }
public function addComposition(HouseholdComposition $composition): self public function addComposition(HouseholdComposition $composition): self

View File

@ -121,7 +121,7 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase
$eventSubscriber->resetPeriodLocation($event); $eventSubscriber->resetPeriodLocation($event);
$this->assertSame($previousAddress, $period->getAddressLocation()); $this->assertNotNull($period->getAddressLocation());
$this->assertNull($period->getPersonLocation()); $this->assertNull($period->getPersonLocation());
} }
@ -166,7 +166,49 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase
$eventSubscriber->resetPeriodLocation($event); $eventSubscriber->resetPeriodLocation($event);
$this->assertSame($previousAddress, $period->getAddressLocation()); $this->assertNotNull($period->getAddressLocation());
$this->assertNull($period->getPersonLocation());
}
public function testEventPersonChangeAddressInThePast()
{
$person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person)
->setUser(new User());
$this->forceIdToPeriod($period);
$membership = new HouseholdMember();
$membership
->setPerson($person)
->setHousehold($household = new Household())
->setStartDate(new DateTimeImmutable('1 year ago'))
;
$previousAddress = new Address();
$previousAddress->setValidFrom(new DateTime('6 months ago'));
$household->addAddress($previousAddress);
$newAddress = new Address();
$newAddress->setValidFrom(new DateTime('tomorrow'));
$household->addAddress($newAddress);
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousAddress($household->getPreviousAddressOf($newAddress))
->setNextAddress($newAddress)
;
$em = $this->prophesize(EntityManagerInterface::class);
$em->persist(Argument::type(Notification::class))->shouldBeCalledTimes(1);
$eventSubscriber = $this->buildSubscriber(null, $em->reveal(), null, null);
$eventSubscriber->resetPeriodLocation($event);
$this->assertNotNull($period->getAddressLocation());
$this->assertNull($period->getPersonLocation()); $this->assertNull($period->getPersonLocation());
} }
@ -201,7 +243,7 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase
$eventSubscriber->resetPeriodLocation($event); $eventSubscriber->resetPeriodLocation($event);
$this->assertSame($previousAddress, $period->getAddressLocation()); $this->assertNotNull($period->getAddressLocation());
$this->assertNull($period->getPersonLocation()); $this->assertNull($period->getPersonLocation());
} }

View File

@ -11,9 +11,11 @@ declare(strict_types=1);
namespace Entity\Household; namespace Entity\Household;
use Chill\MainBundle\Entity\Address;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdComposition; use Chill\PersonBundle\Entity\Household\HouseholdComposition;
use DateTimeImmutable; use DateTimeImmutable;
use DateTime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@ -44,4 +46,43 @@ final class HouseholdTest extends TestCase
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate());
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate()); $this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate());
} }
public function testHouseholdAddressConsistent()
{
$household = new Household();
$lastAddress = new Address();
$lastAddress->setValidFrom($yesterday = new DateTime('yesterday'));
$household->addAddress($lastAddress);
$this->assertNull($lastAddress->getValidTo());
$this->assertEquals($yesterday, $lastAddress->getValidFrom());
$previousAddress = new Address();
$previousAddress->setValidFrom($oneMonthAgo = new DateTime('1 month ago'));
$household->addAddress($previousAddress);
$addresses = $household->getAddressesOrdered();
$this->assertSame($previousAddress, $addresses[0]);
$this->assertSame($lastAddress, $addresses[1]);
$this->assertEquals($oneMonthAgo, $previousAddress->getValidFrom());
$this->assertEquals($yesterday, $previousAddress->getValidTo());
$this->assertEquals($yesterday, $lastAddress->getValidFrom());
$this->assertNull($lastAddress->getValidTo());
$futureAddress = new Address();
$futureAddress->setValidFrom(new DateTime('tomorrow'));
$household->addAddress($futureAddress);
$addresses = $household->getAddressesOrdered();
$this->assertSame($previousAddress, $addresses[0]);
$this->assertSame($lastAddress, $addresses[1]);
$this->assertSame($futureAddress, $addresses[2]);
$this->assertEquals($oneMonthAgo, $previousAddress->getValidFrom());
$this->assertEquals($yesterday, $previousAddress->getValidTo());
$this->assertEquals($yesterday, $lastAddress->getValidFrom());
$this->assertNull($lastAddress->getValidTo());
}
} }

View File

@ -30,11 +30,6 @@ final class PersonAddressMoveEventTest extends TestCase
public function testPersonChangeHousehold() public function testPersonChangeHousehold()
{ {
$person = new Person(); $person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person);
$previousHousehold = (new Household())->addAddress( $previousHousehold = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago')) ($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
@ -71,11 +66,6 @@ final class PersonAddressMoveEventTest extends TestCase
public function testPersonLeaveHousehold() public function testPersonLeaveHousehold()
{ {
$person = new Person(); $person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person);
$previousHousehold = (new Household())->addAddress( $previousHousehold = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago')) ($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
@ -98,4 +88,32 @@ final class PersonAddressMoveEventTest extends TestCase
$this->assertTrue($event->personLeaveWithoutHousehold()); $this->assertTrue($event->personLeaveWithoutHousehold());
$this->assertEquals(new DateTimeImmutable('tomorrow'), $event->getMoveDate()); $this->assertEquals(new DateTimeImmutable('tomorrow'), $event->getMoveDate());
} }
public function testPersonChangeAddress()
{
$person = new Person();
$household = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
);
$household = (new Household())->addAddress(
($nextAddress = new Address())->setValidFrom(new DateTime('1 month ago'))
);
$member = new HouseholdMember();
$member
->setPerson($person)
->setHousehold($household)
->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('tomorrow'));
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousAddress($previousAddress)
->setNextAddress($nextAddress);
$this->assertSame($previousAddress, $event->getPreviousAddress());
$this->assertSame($nextAddress, $event->getNextAddress());
$this->assertEquals(new DateTime('tomorrow'), $nextAddress->getValidFrom());
$this->assertEquals(new DateTime('tomorrow'), $event->getMoveDate());
}
} }