diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php index d788bd6e0..053130240 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php @@ -1,24 +1,51 @@ engine = $engine; + $this->entityManager = $entityManager; + $this->security = $security; + $this->translator = $translator; + } + + public static function getSubscribedEvents(): array { return [ - PersonAddressMoveEvent::class => 'resetPeriodLocation' + PersonAddressMoveEvent::class => 'resetPeriodLocation', ]; } @@ -30,24 +57,33 @@ class PersonMoveEventSubscriber implements EventSubscriberInterface $person = $event->getPerson(); 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->setAddressLocation($event->getPreviousAddress()); - if (null !== $period->getUser() && $period->getUser() !== $this->security->getUser()) { - $notification = new Notification(); - $notification - ->addAddressee($period->getUser()) - ->setTitle($this->translator->trans()) - ->setRelatedEntityClass(AccompanyingPeriod::class) - ->setRelatedEntityId($period->getId()) - ; + $notification = new Notification(); + $notification + ->addAddressee($period->getUser()) + ->setTitle($this->translator->trans('period_notification.Person locating period has moved')) + ->setRelatedEntityClass(AccompanyingPeriod::class) + ->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); } } } } - - } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 11ef74fa8..0012f341d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -217,7 +217,7 @@ class AccompanyingPeriod implements /** * @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class, - * mappedBy="period", cascade="{"persist", "remove""}, orphanRemoval=true) + * mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true) */ private Collection $locationHistories; @@ -709,6 +709,17 @@ class AccompanyingPeriod implements 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. * @@ -723,6 +734,9 @@ class AccompanyingPeriod implements return $this->getAddressLocation(); } + /** + * @return Collection|AccompanyingPeriodLocationHistory[] + */ public function getLocationHistories(): Collection { return $this->locationHistories; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php index 42e57185e..161f5c316 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php @@ -66,6 +66,7 @@ class AccompanyingPeriodParticipation $this->startDate = new DateTime('now'); $this->accompanyingPeriod = $accompanyingPeriod; $this->person = $person; + $person->getAccompanyingPeriodParticipations()->add($this); } public function getAccompanyingPeriod(): ?AccompanyingPeriod diff --git a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php index 6cb34c386..9be02854c 100644 --- a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php +++ b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php @@ -15,21 +15,23 @@ use Chill\MainBundle\Entity\Address; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Person; +use DateTime; +use DateTimeImmutable; use Symfony\Contracts\EventDispatcher\Event; class PersonAddressMoveEvent extends Event { 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 ?Address $previousAddress; + private ?Address $previousAddress = null; - private ?HouseholdMember $previousMembership; + private ?HouseholdMember $previousMembership = null; public function __construct( Person $person @@ -37,8 +39,39 @@ class PersonAddressMoveEvent extends Event $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 { + if (null !== $this->getNextMembership()) { + return $this->getNextMembership()->getHousehold() + ->getCurrentAddress( + $this->getMoveDate() === null ? null : + DateTime::createFromImmutable($this->getMoveDate()) + ); + } + return $this->nextAddress; } @@ -63,6 +96,14 @@ class PersonAddressMoveEvent extends Event public function getPreviousAddress(): ?Address { + if (null !== $this->getPreviousMembership()) { + return $this->getPreviousMembership()->getHousehold() + ->getCurrentAddress( + null === $this->getMoveDate() ? null : + DateTime::createFromImmutable($this->getMoveDate()) + ); + } + return $this->previousAddress; } @@ -85,11 +126,21 @@ class PersonAddressMoveEvent extends Event 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 { return $this->getPreviousHousehold() !== $this->getNextHousehold(); } + public function personLeaveWithoutHousehold(): bool + { + return null === $this->getNextMembership() + && null === $this->getNextAddress(); + } + public function setNextAddress(?Address $nextAddress): PersonAddressMoveEvent { $this->nextAddress = $nextAddress; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig new file mode 100644 index 000000000..e4509d78a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig @@ -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, diff --git a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php new file mode 100644 index 000000000..0ba8cf878 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php @@ -0,0 +1,205 @@ +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); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php new file mode 100644 index 000000000..5347f8c68 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php @@ -0,0 +1,101 @@ +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()); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index c409cbb7b..65199dabf 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -39,49 +39,6 @@ final class MembersEditorTest extends TestCase $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() { $person = new Person(); @@ -168,4 +125,49 @@ final class MembersEditorTest extends TestCase ); $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 + ); + } }