diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 23296e701..cc391c612 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -55,6 +55,14 @@ class MembersEditor $this->eventDispatcher = $eventDispatcher; } + /** + * Add a person to the household + * + * The person is added to the household associated with this editor's instance. + * + * If the person is also a member of another household, or the same household at the same position, the person + * is not associated any more with the previous household. + */ public function addMovement(DateTimeImmutable $date, Person $person, ?Position $position, ?bool $holder = false, ?string $comment = null): self { if (null === $this->household) { @@ -69,68 +77,66 @@ class MembersEditor ->setComment($comment); $this->household->addMember($membership); - if (null !== $position) { - if ($position->getShareHousehold()) { - // launch event only if moving to a "share household" position, - // and if the destination household is different than the previous one - $event = new PersonAddressMoveEvent($person); - $event->setNextMembership($membership); + if ($membership->getShareHousehold()) { + // launch event only if moving to a "share household" position, + // and if the destination household is different than the previous one + $event = new PersonAddressMoveEvent($person); + $event->setNextMembership($membership); - $counter = 0; + $counter = 0; - foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { - if ($participation === $membership) { - continue; - } - - if ($participation->getStartDate() > $membership->getStartDate()) { - continue; - } - - ++$counter; - - if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { - $participation->setEndDate($date); - $this->membershipsAffected[] = $participation; - $this->oldMembershipsHashes[] = spl_object_hash($participation); - - if ($participation->getHousehold() !== $this->household) { - $event->setPreviousMembership($participation); - $this->events[] = $event; - } - } + foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { + if ($participation === $membership) { + continue; } - // send also the event if there was no participation before - if (0 === $counter) { - $this->events[] = $event; + if ($participation->getStartDate() > $membership->getStartDate()) { + continue; } - foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { - if ($participation->getHousehold() === $this->household - && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate() - && $participation->getStartDate() <= $membership->getStartDate() - ) { - $participation->setEndDate($membership->getStartDate()); + ++$counter; + + if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { + $participation->setEndDate($date); + $this->membershipsAffected[] = $participation; + $this->oldMembershipsHashes[] = spl_object_hash($participation); + + if ($participation->getHousehold() !== $this->household) { + $event->setPreviousMembership($participation); + $this->events[] = $event; } } - } else { - // if a members is moved to the same household than the one he belongs to, - // we should make it leave the household - if ($person->getCurrentHousehold($date) === $this->household) { - $this->leaveMovement($date, $person); + } + + // send also the event if there was no participation before + if (0 === $counter) { + $this->events[] = $event; + } + + foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { + if ($participation->getHousehold() === $this->household + && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate() + && $participation->getStartDate() <= $membership->getStartDate() + ) { + $participation->setEndDate($membership->getStartDate()); + } + } + } else { + // if there are multiple belongings not sharing household, close the others + foreach ($person->getHouseholdParticipations() as $participation) { + if ($participation === $membership) { + continue; } - // if there are multiple belongings not sharing household, close the others - foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { - if ($participation === $membership) { - continue; - } - - if ($participation->getHousehold() === $this->household - && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()) - && $participation->getStartDate() <= $membership->getStartDate() - ) { + if ($participation->getHousehold() === $this->household + && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()) + && $participation->getStartDate() <= $membership->getStartDate() + ) { + if ($participation->getShareHousehold()) { + // if a members is moved to the same household than the one he belongs to, + // we should make it leave the household + $this->leaveMovement($date, $person); + } else { $participation->setEndDate($membership->getStartDate()); } } @@ -158,6 +164,15 @@ class MembersEditor return null !== $this->household; } + /** + * Makes a person leave the household. + * + * Makes a person leave the household **associated with this editor**. + * + * @param DateTimeImmutable $date + * @param Person $person + * @return $this + */ public function leaveMovement( DateTimeImmutable $date, Person $person @@ -168,7 +183,8 @@ class MembersEditor $criteria->where( $expr->andX( $expr->lte('startDate', $date), - $expr->isNull('endDate') + $expr->isNull('endDate'), + $expr->eq('shareHousehold', true) ) ); diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index 8ec7ee573..c3fb702e9 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -74,6 +74,55 @@ final class MembersEditorTest extends TestCase $this->assertContains(null, $endDates); } + /** + * We test that a leave move is possible when member startdate is same as current date + * + */ + public function testLeaveMovementInSameHouseholdFromShareHouseholdToNotShareHouseholdOnSameDate() + { + $person = new Person(); + $household = new Household(); + + $factory = $this->buildMembersEditorFactory(); + + $positionSharing = (new Position())->setShareHousehold(true); + $positionNotSharing = (new Position())->setShareHousehold(false); + + // create add move + $editor = $factory->createEditor($household); + $editor->addMovement(new DateTimeImmutable('today'), $person, $positionSharing); + $editor->postMove(); + + self::assertContains($person, $household->getCurrentPersons()); + self::assertSame($household, $person->getCurrentHousehold()); + self::assertCount(1, $household->getMembers()); + + // create leave move + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); + $editor = $factory->createEditor($household); + $editor->addMovement(new DateTimeImmutable('today'), $person, $positionNotSharing); + $editor->postMove(); + + $participations = $household->getMembers(); + self::assertCount(2, $participations); + + $sharing = $participations->filter(fn (HouseholdMember $hm) => $hm->getShareHousehold()); + self::assertCount(1, $sharing); + + $notSharing = $participations->filter(fn (HouseholdMember $hm) => !$hm->getShareHousehold()); + self::assertCount(1, $notSharing); + + self::assertNotNull($sharing[0]->getEndDate()); + self::assertEquals(new DateTimeImmutable('today'), $sharing[0]->getEndDate()); + } + /** * We test here a move for a person:. * @@ -98,8 +147,17 @@ final class MembersEditorTest extends TestCase $this->assertContains($person, $household->getCurrentPersons()); // we do the move to the position not sharing household + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldNotBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); $editor = $factory->createEditor($household2 = new Household()); $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing); + $editor->postMove(); $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold()); $notSharing = $household2->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold()); @@ -118,7 +176,7 @@ final class MembersEditorTest extends TestCase * * which was in a position "sharing household" * * which move to the same household, in a position "not sharing household" */ - public function testMoveFromSharingHouseholdToNotSharingHousehouldInSamehousehold() + public function testMoveFromSharingHouseholdToNotSharingHousehouldInSamehouseholdOnDifferentDate() { $person = new Person(); $household = new Household(); @@ -134,8 +192,17 @@ final class MembersEditorTest extends TestCase $this->assertContains($person, $household->getCurrentPersons()); // we do the move to the position not sharing household + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); $editor = $factory->createEditor($household); $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing); + $editor->postMove(); $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold()); $notSharing = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold()); @@ -148,6 +215,84 @@ final class MembersEditorTest extends TestCase $this->assertContains($person, $notSharing->map($getPerson)); } + /** + * We test here a move for a person:. + * + * * which was in a position "not sharing household" + * * which move to the same household, in a position "sharing household" + */ + public function testMoveFromNotSharingHouseholdToSharingHousehouldInSamehousehold() + { + $person = new Person(); + $household = new Household(); + $positionSharing = (new Position())->setShareHousehold(true); + $positionNotSharing = (new Position())->setShareHousehold(false); + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household); + + // we add the member to the household, at the position "not sharing" + $editor->addMovement(new DateTimeImmutable('1 month ago'), $person, $positionNotSharing); + + // double check that the person is in the household + $this->assertContains($person, $household->getCurrentPersons()); + + // we do the move to the position sharing household + $editor = $factory->createEditor($household); + $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionSharing); + + self::assertCount(2, $household->getMembers()); + + $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold()); + $notSharing = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold()); + + $this->assertCount(0, $notSharing); + $this->assertCount(1, $sharings); + + $getPerson = static fn (HouseholdMember $m) => $m->getPerson(); + + $this->assertContains($person, $sharings->map($getPerson)); + $this->assertNotContains($person, $notSharing->map($getPerson)); + } + + /** + * We test here a move for a person:. + * + * * which was in a position "not sharing household" + * * which move to the same household, in a position "sharing household" + */ + public function testMoveFromNotSharingHouseholdToSharingHousehouldInSamehouseholdOnSameDate() + { + $person = new Person(); + $household = new Household(); + $positionSharing = (new Position())->setShareHousehold(true); + $positionNotSharing = (new Position())->setShareHousehold(false); + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household); + + // we add the member to the household, at the position "not sharing" + $editor->addMovement(new DateTimeImmutable('today'), $person, $positionNotSharing); + + // double check that the person is in the household + $this->assertContains($person, $household->getCurrentPersons()); + + // we do the move to the position sharing household + $editor = $factory->createEditor($household); + $editor->addMovement(new DateTimeImmutable('today'), $person, $positionSharing); + + self::assertCount(2, $household->getMembers()); + + $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold()); + $notSharing = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold()); + + $this->assertCount(0, $notSharing); + $this->assertCount(1, $sharings); + + $getPerson = static fn (HouseholdMember $m) => $m->getPerson(); + + $this->assertContains($person, $sharings->map($getPerson)); + $this->assertNotContains($person, $notSharing->map($getPerson)); + } + public function testMovePersonWithoutSharedHousehold() { $person = new Person(); @@ -235,13 +380,26 @@ final class MembersEditorTest extends TestCase $this->assertEquals($date, $membership1->getEndDate()); } - public function testPostMoveToAPositionNotSharingHousehold() + public function testPostMoveToAPositionNotSharingHouseholdOnSameDay() { $person = new Person(); - $position = (new Position()) + $positionNotSharing = (new Position()) ->setShareHousehold(false); + $positionSharing = (new Position())->setShareHousehold(true); $household1 = new Household(); $household2 = new Household(); + + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household1); + $editor->addMovement(new DateTimeImmutable('today'), $person, $positionSharing); + $editor->postMove(); + + self::assertContains($person, $household1->getCurrentPersons()); + self::assertContains($person, $household1->getCurrentMembers() + ->filter(fn (HouseholdMember $m) => $m->getShareHousehold()) + ->map(fn (HouseholdMember $m) => $m->getPerson())); + self::assertSame($household1, $person->getCurrentHousehold()); + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); $eventDispatcher ->dispatch(Argument::type(PersonAddressMoveEvent::class)) @@ -250,11 +408,78 @@ final class MembersEditorTest extends TestCase $eventDispatcher->reveal(), null ); - $editor = $factory->createEditor($household1); - - $editor->addMovement(new DateTimeImmutable('now'), $person, $position); + $editor = $factory->createEditor($household2); + $editor->addMovement(new DateTimeImmutable('today'), $person, $positionNotSharing); $editor->postMove(); + + // $household1 still contains $person + self::assertContains($person, $household1->getCurrentPersons()); + self::assertContains($person, $household1->getCurrentMembers() + ->filter(fn (HouseholdMember $m) => $m->getShareHousehold()) + ->map(fn (HouseholdMember $m) => $m->getPerson())); + self::assertSame($household1, $person->getCurrentHousehold()); + + // $household2 contains $person at non-sharing position + self::assertContains($person, $household2->getCurrentMembers() + ->filter(fn (HouseholdMember $m) => !$m->getShareHousehold()) + ->map(fn (HouseholdMember $m) => $m->getPerson())); + self::assertContains( + $household2, + $person->getHouseholdParticipationsNotShareHousehold() + ->map(fn (HouseholdMember $hm) => $hm->getHousehold()) + ); + } + + public function testPostMoveToAPositionNotSharingHouseholdOnDifferentDays() + { + $person = new Person(); + $positionNotSharing = (new Position()) + ->setShareHousehold(false); + $positionSharing = (new Position())->setShareHousehold(true); + $household1 = new Household(); + $household2 = new Household(); + + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household1); + $editor->addMovement(new DateTimeImmutable('1 year ago'), $person, $positionSharing); + $editor->postMove(); + + self::assertContains($person, $household1->getCurrentPersons()); + self::assertContains($person, $household1->getCurrentMembers() + ->filter(fn (HouseholdMember $m) => $m->getShareHousehold()) + ->map(fn (HouseholdMember $m) => $m->getPerson())); + self::assertSame($household1, $person->getCurrentHousehold()); + + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldNotBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); + $editor = $factory->createEditor($household2); + + $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing); + $editor->postMove(); + + // $household1 still contains $person + self::assertContains($person, $household1->getCurrentPersons()); + self::assertContains($person, $household1->getCurrentMembers() + ->filter(fn (HouseholdMember $m) => $m->getShareHousehold()) + ->map(fn (HouseholdMember $m) => $m->getPerson())); + self::assertSame($household1, $person->getCurrentHousehold()); + + // $household2 contains $person at non-sharing position + self::assertContains($person, $household2->getCurrentMembers() + ->filter(fn (HouseholdMember $m) => !$m->getShareHousehold()) + ->map(fn (HouseholdMember $m) => $m->getPerson())); + self::assertContains( + $household2, + $person->getHouseholdParticipationsNotShareHousehold() + ->map(fn (HouseholdMember $hm) => $hm->getHousehold()) + ); } public function testPostMoveToAPositionSharingHouseholdAndSameHousehold()