handle event PersonMoveEvent on Period

This commit is contained in:
Julien Fastré 2022-02-15 23:52:15 +01:00
parent 1658fee090
commit 0a4913f341
8 changed files with 495 additions and 63 deletions

View File

@ -1,24 +1,51 @@
<?php <?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\AccompanyingPeriod\Events; namespace Chill\PersonBundle\AccompanyingPeriod\Events;
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;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class PersonMoveEventSubscriber implements EventSubscriberInterface class PersonMoveEventSubscriber implements EventSubscriberInterface
{ {
private TranslatorInterface $translator; private EngineInterface $engine;
private EntityManagerInterface $entityManager;
private Security $security; private Security $security;
public static function getSubscribedEvents() private TranslatorInterface $translator;
public function __construct(
EngineInterface $engine,
EntityManagerInterface $entityManager,
Security $security,
TranslatorInterface $translator
) {
$this->engine = $engine;
$this->entityManager = $entityManager;
$this->security = $security;
$this->translator = $translator;
}
public static function getSubscribedEvents(): array
{ {
return [ return [
PersonAddressMoveEvent::class => 'resetPeriodLocation' PersonAddressMoveEvent::class => 'resetPeriodLocation',
]; ];
} }
@ -30,24 +57,33 @@ class PersonMoveEventSubscriber implements EventSubscriberInterface
$person = $event->getPerson(); $person = $event->getPerson();
foreach ($person->getCurrentAccompanyingPeriods() as $period) { foreach ($person->getCurrentAccompanyingPeriods() as $period) {
if ($period->getPersonLocation() === $person) { if ($period->getStep() === AccompanyingPeriod::STEP_DRAFT) {
continue;
}
if ($period->getPersonLocation() === $person
&& $event->getMoveDate() >= $period->getLastLocationHistory()->getStartDate()
&& null !== $period->getUser()
&& $period->getUser() !== $this->security->getUser()
) {
// reset the location, back to an address
$period->setPersonLocation(null); $period->setPersonLocation(null);
$period->setAddressLocation($event->getPreviousAddress()); $period->setAddressLocation($event->getPreviousAddress());
if (null !== $period->getUser() && $period->getUser() !== $this->security->getUser()) { $notification = new Notification();
$notification = new Notification(); $notification
$notification ->addAddressee($period->getUser())
->addAddressee($period->getUser()) ->setTitle($this->translator->trans('period_notification.Person locating period has moved'))
->setTitle($this->translator->trans()) ->setRelatedEntityClass(AccompanyingPeriod::class)
->setRelatedEntityClass(AccompanyingPeriod::class) ->setRelatedEntityId($period->getId())
->setRelatedEntityId($period->getId()) ->setMessage($this->engine->render('@ChillPerson/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig', [
; 'oldPersonLocation' => $person,
'period' => $period,
]));
} $this->entityManager->persist($notification);
} }
} }
} }
} }
} }

View File

@ -217,7 +217,7 @@ class AccompanyingPeriod implements
/** /**
* @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class, * @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class,
* mappedBy="period", cascade="{"persist", "remove""}, orphanRemoval=true) * mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
*/ */
private Collection $locationHistories; private Collection $locationHistories;
@ -709,6 +709,17 @@ class AccompanyingPeriod implements
return $this->job; return $this->job;
} }
public function getLastLocationHistory(): ?AccompanyingPeriodLocationHistory
{
foreach ($this->getLocationHistories() as $locationHistory) {
if (null === $locationHistory->getEndDate()) {
return $locationHistory;
}
}
return null;
}
/** /**
* Get the location, taking precedence into account. * Get the location, taking precedence into account.
* *
@ -723,6 +734,9 @@ class AccompanyingPeriod implements
return $this->getAddressLocation(); return $this->getAddressLocation();
} }
/**
* @return Collection|AccompanyingPeriodLocationHistory[]
*/
public function getLocationHistories(): Collection public function getLocationHistories(): Collection
{ {
return $this->locationHistories; return $this->locationHistories;

View File

@ -66,6 +66,7 @@ class AccompanyingPeriodParticipation
$this->startDate = new DateTime('now'); $this->startDate = new DateTime('now');
$this->accompanyingPeriod = $accompanyingPeriod; $this->accompanyingPeriod = $accompanyingPeriod;
$this->person = $person; $this->person = $person;
$person->getAccompanyingPeriodParticipations()->add($this);
} }
public function getAccompanyingPeriod(): ?AccompanyingPeriod public function getAccompanyingPeriod(): ?AccompanyingPeriod

View File

@ -15,21 +15,23 @@ use Chill\MainBundle\Entity\Address;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime;
use DateTimeImmutable;
use Symfony\Contracts\EventDispatcher\Event; use Symfony\Contracts\EventDispatcher\Event;
class PersonAddressMoveEvent extends Event class PersonAddressMoveEvent extends Event
{ {
public const PERSON_MOVE_POST = 'chill_person.person_move_post'; public const PERSON_MOVE_POST = 'chill_person.person_move_post';
private ?Address $nextAddress; private ?Address $nextAddress = null;
private ?HouseholdMember $nextMembership; private ?HouseholdMember $nextMembership = null;
private Person $person; private Person $person;
private ?Address $previousAddress; private ?Address $previousAddress = null;
private ?HouseholdMember $previousMembership; private ?HouseholdMember $previousMembership = null;
public function __construct( public function __construct(
Person $person Person $person
@ -37,8 +39,39 @@ class PersonAddressMoveEvent extends Event
$this->person = $person; $this->person = $person;
} }
/**
* Get the date of the move.
*
* It might be either:
*
* * the date of the new membership;
* * or the date of the move
* * or the date when the household leaving take place (the end date of the previous membership)
*/
public function getMoveDate(): DateTimeImmutable
{
if ($this->personLeaveWithoutHousehold()) {
return $this->getPreviousMembership()->getEndDate();
}
if ($this->personChangeHousehold()) {
return $this->getNextMembership()->getStartDate();
}
// person is changing address without household
return DateTimeImmutable::createFromMutable($this->getNextAddress()->getValidFrom());
}
public function getNextAddress(): ?Address public function getNextAddress(): ?Address
{ {
if (null !== $this->getNextMembership()) {
return $this->getNextMembership()->getHousehold()
->getCurrentAddress(
$this->getMoveDate() === null ? null :
DateTime::createFromImmutable($this->getMoveDate())
);
}
return $this->nextAddress; return $this->nextAddress;
} }
@ -63,6 +96,14 @@ class PersonAddressMoveEvent extends Event
public function getPreviousAddress(): ?Address public function getPreviousAddress(): ?Address
{ {
if (null !== $this->getPreviousMembership()) {
return $this->getPreviousMembership()->getHousehold()
->getCurrentAddress(
null === $this->getMoveDate() ? null :
DateTime::createFromImmutable($this->getMoveDate())
);
}
return $this->previousAddress; return $this->previousAddress;
} }
@ -85,11 +126,21 @@ class PersonAddressMoveEvent extends Event
return $this->getPreviousAddress() !== $this->getNextAddress(); return $this->getPreviousAddress() !== $this->getNextAddress();
} }
/**
* Return true if the user change household (this include the fact that a person
* leave household without a new one).
*/
public function personChangeHousehold(): bool public function personChangeHousehold(): bool
{ {
return $this->getPreviousHousehold() !== $this->getNextHousehold(); return $this->getPreviousHousehold() !== $this->getNextHousehold();
} }
public function personLeaveWithoutHousehold(): bool
{
return null === $this->getNextMembership()
&& null === $this->getNextAddress();
}
public function setNextAddress(?Address $nextAddress): PersonAddressMoveEvent public function setNextAddress(?Address $nextAddress): PersonAddressMoveEvent
{ {
$this->nextAddress = $nextAddress; $this->nextAddress = $nextAddress;

View File

@ -0,0 +1,22 @@
{#
content of the notification if the person move and the person "localize" the period
#}{{ period.user.label }},
L'usager {{ oldPersonLocation|chill_entity_render_string }} a déménagé.
Son adresse était utilisée pour localiser le parcours n°{{ period.id }}, dont vous êtes
le référent.
En conséquence de ce déménage, le parcours est toujours localisé à cette adresse, mais à l'aide d'une
adresse temporaire.
Si vous continuez à suivre le parcours, vous pouvez le localiser à nouveau auprès de l'adresse de
l'usager {{ oldPersonLocation|chill_entity_render_string }}.
Sinon, veillez à vous assurer de la continuité du suivi par vos collègues.
Pour visualiser le parcours, cliquez ici:
{{ absolute_url(path('chill_person_accompanying_course_index', {'accompanying_period_id': period.id})) }}
Cordialement,

View File

@ -0,0 +1,205 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace AccompanyingPeriod\Events;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\AccompanyingPeriod\Events\PersonMoveEventSubscriber;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
use DateTime;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use ReflectionClass;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
* @coversNothing
*/
final class PersonMoveEventSubscriberTest extends KernelTestCase
{
use ProphecyTrait;
public function testEventChangeHouseholdNoNotificationForPeriodWithoutUser()
{
$person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person);
$this->forceIdToPeriod($period);
$previousHousehold = (new Household())->addAddress(
(new Address())->setValidFrom(new DateTime('1 year ago'))
);
$previousMembership = new HouseholdMember();
$previousMembership
->setPerson($person)
->setHousehold($previousHousehold)
->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('tomorrow'));
$nextHousehold = (new Household())->addAddress(
(new Address())->setValidFrom(new DateTime('tomorrow'))
);
$nextMembership = new HouseholdMember();
$nextMembership
->setPerson($person)
->setHousehold($nextHousehold)
->setStartDate(new DateTimeImmutable('tomorrow'));
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousMembership($previousMembership)
->setNextMembership($nextMembership);
$em = $this->prophesize(EntityManagerInterface::class);
$em->persist(Argument::type(Notification::class))->shouldNotBeCalled();
$eventSubscriber = $this->buildSubscriber(null, $em->reveal(), null, null);
$eventSubscriber->resetPeriodLocation($event);
}
public function testEventChangeHouseholdNotification()
{
$person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person)
->setUser(new User());
$this->forceIdToPeriod($period);
$previousHousehold = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
);
$previousMembership = new HouseholdMember();
$previousMembership
->setPerson($person)
->setHousehold($previousHousehold)
->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('tomorrow'));
$nextHousehold = (new Household())->addAddress(
(new Address())->setValidFrom(new DateTime('tomorrow'))
);
$nextMembership = new HouseholdMember();
$nextMembership
->setPerson($person)
->setHousehold($nextHousehold)
->setStartDate(new DateTimeImmutable('tomorrow'));
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousMembership($previousMembership)
->setNextMembership($nextMembership);
$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->assertSame($previousAddress, $period->getAddressLocation());
$this->assertNull($period->getPersonLocation());
}
public function testEventLeaveNotification()
{
$person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person)
->setUser(new User());
$this->forceIdToPeriod($period);
$previousHousehold = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
);
$previousMembership = new HouseholdMember();
$previousMembership
->setPerson($person)
->setHousehold($previousHousehold)
->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('tomorrow'));
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousMembership($previousMembership);
$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->assertSame($previousAddress, $period->getAddressLocation());
$this->assertNull($period->getPersonLocation());
}
private function buildSubscriber(
?EngineInterface $engine = null,
?EntityManagerInterface $entityManager = null,
?Security $security = null,
?TranslatorInterface $translator = null
): PersonMoveEventSubscriber {
if (null === $translator) {
$double = $this->prophesize(TranslatorInterface::class);
$translator = $double->reveal();
}
if (null === $security) {
$double = $this->prophesize(Security::class);
$double->getUser()->willReturn(new User());
$security = $double->reveal();
}
if (null === $engine) {
$double = $this->prophesize(EngineInterface::class);
$engine = $double->reveal();
}
if (null === $entityManager) {
$double = $this->prophesize(EntityManagerInterface::class);
$entityManager = $double->reveal();
}
return new PersonMoveEventSubscriber(
$engine,
$entityManager,
$security,
$translator
);
}
private function forceIdToPeriod(AccompanyingPeriod $period): void
{
$reflectionClass = new ReflectionClass($period);
$property = $reflectionClass->getProperty('id');
$property->setAccessible(true);
$property->setValue($period, 0);
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Event\Person;
use Chill\MainBundle\Entity\Address;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
use DateTime;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
final class PersonAddressMoveEventTest extends TestCase
{
public function testPersonChangeHousehold()
{
$person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person);
$previousHousehold = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
);
$previousMembership = new HouseholdMember();
$previousMembership
->setPerson($person)
->setHousehold($previousHousehold)
->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('tomorrow'));
$nextHousehold = (new Household())->addAddress(
($nextAddress = new Address())->setValidFrom(new DateTime('tomorrow'))
);
$nextMembership = new HouseholdMember();
$nextMembership
->setPerson($person)
->setHousehold($nextHousehold)
->setStartDate(new DateTimeImmutable('tomorrow'));
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousMembership($previousMembership)
->setNextMembership($nextMembership);
$this->assertTrue($event->personChangeHousehold());
$this->assertSame($previousAddress, $event->getPreviousAddress());
$this->assertSame($nextAddress, $event->getNextAddress());
$this->assertTrue($event->personChangeAddress());
$this->assertFalse($event->personLeaveWithoutHousehold());
$this->assertEquals(new DateTimeImmutable('tomorrow'), $event->getMoveDate());
}
public function testPersonLeaveHousehold()
{
$person = new Person();
$period = new AccompanyingPeriod();
$period
->setStep(AccompanyingPeriod::STEP_CONFIRMED)
->setPersonLocation($person)
->addPerson($person);
$previousHousehold = (new Household())->addAddress(
($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago'))
);
$previousMembership = new HouseholdMember();
$previousMembership
->setPerson($person)
->setHousehold($previousHousehold)
->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('tomorrow'));
$event = new PersonAddressMoveEvent($person);
$event
->setPreviousMembership($previousMembership);
$this->assertTrue($event->personChangeHousehold());
$this->assertSame($previousAddress, $event->getPreviousAddress());
$this->assertNull($event->getNextAddress());
$this->assertTrue($event->personChangeAddress());
$this->assertTrue($event->personLeaveWithoutHousehold());
$this->assertEquals(new DateTimeImmutable('tomorrow'), $event->getMoveDate());
}
}

View File

@ -39,49 +39,6 @@ final class MembersEditorTest extends TestCase
$this->factory = $this->buildMembersEditorFactory(); $this->factory = $this->buildMembersEditorFactory();
} }
private function buildMembersEditorFactory(
?EventDispatcherInterface $eventDispatcher = null,
?ValidatorInterface $validator = null
) {
if ($eventDispatcher === null) {
$double = $this->getProphet()->prophesize();
$double->willImplement(EventDispatcherInterface::class);
$double->dispatch(Argument::type(PersonAddressMoveEvent::class));
$eventDispatcher = $double->reveal();
}
if ($validator === null) {
$double = $this->getProphet()->prophesize();
$double->willImplement(ValidatorInterface::class);
$validator = $double->reveal();
}
return new MembersEditorFactory(
$eventDispatcher, $validator
);
}
public function testPostMove()
{
$person = new Person();
$position = (new Position())
->setShareHousehold(false);
$household1 = new Household();
$household2 = new Household();
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldBeCalled();
$factory = $this->buildMembersEditorFactory(
$eventDispatcher->reveal(), null
);
$editor = $factory->createEditor($household1);
$editor->addMovement(new DateTimeImmutable('now'), $person, $position);
$editor->postMove();
}
public function testMovePersonWithoutSharedHousehold() public function testMovePersonWithoutSharedHousehold()
{ {
$person = new Person(); $person = new Person();
@ -168,4 +125,49 @@ final class MembersEditorTest extends TestCase
); );
$this->assertEquals($date, $membership1->getEndDate()); $this->assertEquals($date, $membership1->getEndDate());
} }
public function testPostMove()
{
$person = new Person();
$position = (new Position())
->setShareHousehold(false);
$household1 = new Household();
$household2 = new Household();
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldBeCalled();
$factory = $this->buildMembersEditorFactory(
$eventDispatcher->reveal(),
null
);
$editor = $factory->createEditor($household1);
$editor->addMovement(new DateTimeImmutable('now'), $person, $position);
$editor->postMove();
}
private function buildMembersEditorFactory(
?EventDispatcherInterface $eventDispatcher = null,
?ValidatorInterface $validator = null
) {
if (null === $eventDispatcher) {
$double = $this->getProphet()->prophesize();
$double->willImplement(EventDispatcherInterface::class);
$double->dispatch(Argument::type(PersonAddressMoveEvent::class));
$eventDispatcher = $double->reveal();
}
if (null === $validator) {
$double = $this->getProphet()->prophesize();
$double->willImplement(ValidatorInterface::class);
$validator = $double->reveal();
}
return new MembersEditorFactory(
$eventDispatcher,
$validator
);
}
} }