From 441704dc29e6518a8d8ffefca39aebe2d6e130b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 11 Feb 2022 10:31:17 +0100 Subject: [PATCH 01/18] first impl of event (WIP) --- .../Entity/AccompanyingPeriod.php | 36 ++++- .../AccompanyingPeriodLocationHistory.php | 144 ++++++++++++++++++ .../Event/Person/PersonAddressMoveEvent.php | 119 +++++++++++++++ .../Household/MembersEditor.php | 23 ++- .../Household/MembersEditorFactory.php | 14 +- 5 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php create mode 100644 src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 1e7444af6..00a5ca580 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -21,6 +21,7 @@ use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\UserJob; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodLocationHistory; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; @@ -113,6 +114,12 @@ class AccompanyingPeriod implements */ public const STEP_DRAFT = 'DRAFT'; + /** + * @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class, + * mappedBy="period") + */ + private Collection $locationHistories; + /** * @ORM\ManyToOne( * targetEntity=Address::class @@ -384,6 +391,7 @@ class AccompanyingPeriod implements $this->works = new ArrayCollection(); $this->resources = new ArrayCollection(); $this->userHistories = new ArrayCollection(); + $this->locationHistories = new ArrayCollection(); } /** @@ -1036,7 +1044,9 @@ class AccompanyingPeriod implements */ public function setAddressLocation(?Address $addressLocation = null): self { - $this->addressLocation = $addressLocation; + if ($this->addressLocation !== $addressLocation) { + $this->addressLocation = $addressLocation; + } return $this; } @@ -1259,4 +1269,28 @@ class AccompanyingPeriod implements return $this; } + + public function getLocationHistories(): Collection + { + return $this->locationHistories; + } + + public function addLocationHistory(AccompanyingPeriodLocationHistory $history): self + { + if (!$this->locationHistories->contains($history)) { + $this->locationHistories[] = $history; + $history->setPeriod($this); + } + + return $this; + } + + public function removeLocationHistory(AccompanyingPeriodLocationHistory $history): self + { + if ($this->locationHistories->removeElement($history)) { + $history->setPeriod(null); + } + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php new file mode 100644 index 000000000..4ceac647d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php @@ -0,0 +1,144 @@ +id; + } + + /** + * @return Person|null + */ + public function getPersonLocation(): ?Person + { + return $this->personLocation; + } + + /** + * @param Person|null $personLocation + * @return AccompanyingPeriodLocationHistory + */ + public function setPersonLocation(?Person $personLocation): AccompanyingPeriodLocationHistory + { + $this->personLocation = $personLocation; + return $this; + } + + /** + * @return Address|null + */ + public function getAddressLocation(): ?Address + { + return $this->addressLocation; + } + + /** + * @param Address|null $addressLocation + * @return AccompanyingPeriodLocationHistory + */ + public function setAddressLocation(?Address $addressLocation): AccompanyingPeriodLocationHistory + { + $this->addressLocation = $addressLocation; + return $this; + } + + /** + * @return \DateTimeImmutable|null + */ + public function getStartDate(): ?\DateTimeImmutable + { + return $this->startDate; + } + + /** + * @param \DateTimeImmutable|null $startDate + * @return AccompanyingPeriodLocationHistory + */ + public function setStartDate(?\DateTimeImmutable $startDate): AccompanyingPeriodLocationHistory + { + $this->startDate = $startDate; + return $this; + } + + /** + * @return \DateTimeImmutable|null + */ + public function getEndDate(): ?\DateTimeImmutable + { + return $this->endDate; + } + + /** + * @param \DateTimeImmutable|null $endDate + * @return AccompanyingPeriodLocationHistory + */ + public function setEndDate(?\DateTimeImmutable $endDate): AccompanyingPeriodLocationHistory + { + $this->endDate = $endDate; + return $this; + } + + /** + * @return AccompanyingPeriod + */ + public function getPeriod(): AccompanyingPeriod + { + return $this->period; + } + + /** + * @internal use AccompanyingPeriod::addLocationHistory + */ + public function setPeriod(AccompanyingPeriod $period): AccompanyingPeriodLocationHistory + { + $this->period = $period; + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php new file mode 100644 index 000000000..afd3ad37d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php @@ -0,0 +1,119 @@ +person = $person; + } + + /** + * @param Address|null $previousAddress + * @return PersonAddressMoveEvent + */ + public function setPreviousAddress(?Address $previousAddress): PersonAddressMoveEvent + { + $this->previousAddress = $previousAddress; + return $this; + } + + + public function getPerson(): Person + { + return $this->person; + } + + public function getPreviousAddress(): ?Address + { + return $this->previousAddress; + } + + public function getPreviousHousehold(): ?Household + { + if (null !== $previousMembership = $this->getPreviousMembership()) { + return $previousMembership->getHousehold(); + } + + return null; + } + + public function getNextHousehold(): ?Household + { + if (NULL !== $nextMembership = $this->getNextMembership()) { + return $nextMembership->getHousehold(); + } + + return null; + } + + public function personChangeHousehold(): bool + { + return $this->getPreviousHousehold() !== $this->getNextHousehold(); + } + + public function personChangeAddress(): bool + { + return $this->getPreviousAddress() !== $this->getNextAddress(); + } + + /** + * @return HouseholdMember|null + */ + public function getPreviousMembership(): ?HouseholdMember + { + return $this->previousMembership; + } + + /** + * @param HouseholdMember|null $previousMembership + * @return PersonAddressMoveEvent + */ + public function setPreviousMembership(?HouseholdMember $previousMembership): PersonAddressMoveEvent + { + $this->previousMembership = $previousMembership; + return $this; + } + + public function getNextMembership(): ?HouseholdMember + { + return $this->nextMembership; + } + + public function setNextMembership(?HouseholdMember $nextMembership): PersonAddressMoveEvent + { + $this->nextMembership = $nextMembership; + return $this; + } + + public function getNextAddress(): ?Address + { + return $this->nextAddress; + } + + public function setNextAddress(?Address $nextAddress): PersonAddressMoveEvent + { + $this->nextAddress = $nextAddress; + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index adc97a4ac..d3a777471 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -15,13 +15,13 @@ use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; use DateTimeImmutable; use Doctrine\Common\Collections\Criteria; use LogicException; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; - use function in_array; use function spl_object_hash; @@ -41,12 +41,17 @@ class MembersEditor private array $persistables = []; + private array $events = []; + private ValidatorInterface $validator; - public function __construct(ValidatorInterface $validator, ?Household $household) + private EventDispatcherInterface $eventDispatcher; + + public function __construct(ValidatorInterface $validator, ?Household $household, EventDispatcherInterface $eventDispatcher) { $this->validator = $validator; $this->household = $household; + $this->eventDispatcher = $eventDispatcher; } public function addMovement(DateTimeImmutable $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self @@ -55,6 +60,8 @@ class MembersEditor throw new LogicException('You must define a household first'); } + $event = new PersonAddressMoveEvent($person); + $membership = (new HouseholdMember()) ->setStartDate($date) ->setPerson($person) @@ -62,6 +69,7 @@ class MembersEditor ->setHolder($holder) ->setComment($comment); $this->household->addMember($membership); + $event->setNextMembership($membership); if ($position->getShareHousehold()) { foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { @@ -74,6 +82,7 @@ class MembersEditor } if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { + $event->setPreviousMembership($participation); $participation->setEndDate($date); $this->membershipsAffected[] = $participation; $this->oldMembershipsHashes[] = spl_object_hash($participation); @@ -92,6 +101,7 @@ class MembersEditor $this->membershipsAffected[] = $membership; $this->persistables[] = $membership; + $this->events[] = $event; return $this; } @@ -129,6 +139,8 @@ class MembersEditor ->matching($criteria); foreach ($participations as $participation) { + $this->events[] = $event = new PersonAddressMoveEvent(); + $event->setPreviousMembership($participation); $participation->setEndDate($date); $this->membershipsAffected[] = $participation; } @@ -136,6 +148,13 @@ class MembersEditor return $this; } + public function postMove(): void + { + foreach ($this->events as $event) { + $this->eventDispatcher->dispatch($event); + } + } + public function validate(): ConstraintViolationListInterface { if ($this->hasHousehold()) { diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php index a26841064..366b1cb4c 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php @@ -12,17 +12,25 @@ declare(strict_types=1); namespace Chill\PersonBundle\Household; use Chill\PersonBundle\Entity\Household\Household; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; class MembersEditorFactory { - public function __construct(ValidatorInterface $validator) - { + private EventDispatcherInterface $eventDispatcher; + + private ValidatorInterface $validator; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + ValidatorInterface $validator + ) { $this->validator = $validator; + $this->eventDispatcher = $eventDispatcher; } public function createEditor(?Household $household = null): MembersEditor { - return new MembersEditor($this->validator, $household); + return new MembersEditor($this->validator, $household, $this->eventDispatcher); } } From b9dbb1916a6877042f37fcc4dac3380e077904d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Feb 2022 23:03:40 +0100 Subject: [PATCH 02/18] migration for history, and create history location on each change --- .../Entity/AccompanyingPeriod.php | 126 +++++++++++---- .../AccompanyingPeriodLocationHistory.php | 152 ++++++++---------- .../Event/Person/PersonAddressMoveEvent.php | 107 ++++++------ .../Household/MembersEditor.php | 8 +- .../Tests/Entity/AccompanyingPeriodTest.php | 58 +++++++ .../migrations/Version20220214200327.php | 46 ++++++ 6 files changed, 324 insertions(+), 173 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 00a5ca580..11ef74fa8 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -38,17 +38,19 @@ use DateTimeImmutable; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; +use Iterator; use LogicException; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\GroupSequenceProviderInterface; + use UnexpectedValueException; use function in_array; - use const SORT_REGULAR; /** @@ -114,12 +116,6 @@ class AccompanyingPeriod implements */ public const STEP_DRAFT = 'DRAFT'; - /** - * @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class, - * mappedBy="period") - */ - private Collection $locationHistories; - /** * @ORM\ManyToOne( * targetEntity=Address::class @@ -219,6 +215,12 @@ class AccompanyingPeriod implements */ private ?UserJob $job = null; + /** + * @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class, + * mappedBy="period", cascade="{"persist", "remove""}, orphanRemoval=true) + */ + private Collection $locationHistories; + /** * @var DateTime * @@ -442,6 +444,39 @@ class AccompanyingPeriod implements return $this; } + public function addLocationHistory(AccompanyingPeriodLocationHistory $history): self + { + if ($this->getStep() === self::STEP_DRAFT) { + return $this; + } + + if (!$this->locationHistories->contains($history)) { + $this->locationHistories[] = $history; + $history->setPeriod($this); + } + + // ensure continuity of histories + $criteria = new Criteria(); + $criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]); + + /** @var Iterator $locations */ + $locations = $this->getLocationHistories()->matching($criteria)->getIterator(); + $locations->rewind(); + + do { + /** @var AccompanyingPeriodLocationHistory $current */ + $current = $locations->current(); + $locations->next(); + + if ($locations->valid()) { + $next = $locations->current(); + $current->setEndDate($next->getStartDate()); + } + } while ($locations->valid()); + + return $this; + } + public function addPerson(?Person $person = null): self { if (null !== $person) { @@ -688,6 +723,11 @@ class AccompanyingPeriod implements return $this->getAddressLocation(); } + public function getLocationHistories(): Collection + { + return $this->locationHistories; + } + /** * Get where the location is. * @@ -990,6 +1030,15 @@ class AccompanyingPeriod implements $this->comments->removeElement($comment); } + public function removeLocationHistory(AccompanyingPeriodLocationHistory $history): self + { + if ($this->locationHistories->removeElement($history)) { + $history->setPeriod(null); + } + + return $this; + } + /** * Remove Participation. */ @@ -1046,6 +1095,15 @@ class AccompanyingPeriod implements { if ($this->addressLocation !== $addressLocation) { $this->addressLocation = $addressLocation; + + if (null !== $addressLocation) { + $locationHistory = new AccompanyingPeriodLocationHistory(); + $locationHistory + ->setStartDate(new DateTimeImmutable('now')) + ->setAddressLocation($addressLocation); + + $this->addLocationHistory($locationHistory); + } } return $this; @@ -1149,7 +1207,18 @@ class AccompanyingPeriod implements */ public function setPersonLocation(?Person $person = null): self { - $this->personLocation = $person; + if ($this->personLocation !== $person) { + $this->personLocation = $person; + + if (null !== $person) { + $locationHistory = new AccompanyingPeriodLocationHistory(); + $locationHistory + ->setStartDate(new DateTimeImmutable('now')) + ->setPersonLocation($person); + + $this->addLocationHistory($locationHistory); + } + } return $this; } @@ -1216,8 +1285,14 @@ class AccompanyingPeriod implements public function setStep(string $step): self { + $previous = $this->step; + $this->step = $step; + if (self::STEP_DRAFT === $previous && self::STEP_DRAFT !== $step) { + $this->bootPeriod(); + } + return $this; } @@ -1256,6 +1331,17 @@ class AccompanyingPeriod implements return $this; } + private function bootPeriod(): void + { + // first location history + $locationHistory = new AccompanyingPeriodLocationHistory(); + $locationHistory + ->setStartDate(new DateTimeImmutable('now')) + ->setPersonLocation($this->getPersonLocation()) + ->setAddressLocation($this->getAddressLocation()); + $this->addLocationHistory($locationHistory); + } + private function setRequestorPerson(?Person $requestorPerson = null): self { $this->requestorPerson = $requestorPerson; @@ -1269,28 +1355,4 @@ class AccompanyingPeriod implements return $this; } - - public function getLocationHistories(): Collection - { - return $this->locationHistories; - } - - public function addLocationHistory(AccompanyingPeriodLocationHistory $history): self - { - if (!$this->locationHistories->contains($history)) { - $this->locationHistories[] = $history; - $history->setPeriod($this); - } - - return $this; - } - - public function removeLocationHistory(AccompanyingPeriodLocationHistory $history): self - { - if ($this->locationHistories->removeElement($history)) { - $history->setPeriod(null); - } - - return $this; - } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php index 4ceac647d..8bef79050 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php @@ -1,18 +1,41 @@ id; - } - - /** - * @return Person|null - */ - public function getPersonLocation(): ?Person - { - return $this->personLocation; - } - - /** - * @param Person|null $personLocation - * @return AccompanyingPeriodLocationHistory - */ - public function setPersonLocation(?Person $personLocation): AccompanyingPeriodLocationHistory - { - $this->personLocation = $personLocation; - return $this; - } - - /** - * @return Address|null - */ public function getAddressLocation(): ?Address { return $this->addressLocation; } - /** - * @param Address|null $addressLocation - * @return AccompanyingPeriodLocationHistory - */ - public function setAddressLocation(?Address $addressLocation): AccompanyingPeriodLocationHistory - { - $this->addressLocation = $addressLocation; - return $this; - } - - /** - * @return \DateTimeImmutable|null - */ - public function getStartDate(): ?\DateTimeImmutable - { - return $this->startDate; - } - - /** - * @param \DateTimeImmutable|null $startDate - * @return AccompanyingPeriodLocationHistory - */ - public function setStartDate(?\DateTimeImmutable $startDate): AccompanyingPeriodLocationHistory - { - $this->startDate = $startDate; - return $this; - } - - /** - * @return \DateTimeImmutable|null - */ - public function getEndDate(): ?\DateTimeImmutable + public function getEndDate(): ?DateTimeImmutable { return $this->endDate; } - /** - * @param \DateTimeImmutable|null $endDate - * @return AccompanyingPeriodLocationHistory - */ - public function setEndDate(?\DateTimeImmutable $endDate): AccompanyingPeriodLocationHistory + public function getId(): ?int { - $this->endDate = $endDate; - return $this; + return $this->id; } - /** - * @return AccompanyingPeriod - */ public function getPeriod(): AccompanyingPeriod { return $this->period; } + public function getPersonLocation(): ?Person + { + return $this->personLocation; + } + + public function getStartDate(): ?DateTimeImmutable + { + return $this->startDate; + } + + public function setAddressLocation(?Address $addressLocation): AccompanyingPeriodLocationHistory + { + $this->addressLocation = $addressLocation; + + return $this; + } + + public function setEndDate(?DateTimeImmutable $endDate): AccompanyingPeriodLocationHistory + { + $this->endDate = $endDate; + + return $this; + } + /** * @internal use AccompanyingPeriod::addLocationHistory */ public function setPeriod(AccompanyingPeriod $period): AccompanyingPeriodLocationHistory { $this->period = $period; + + return $this; + } + + public function setPersonLocation(?Person $personLocation): AccompanyingPeriodLocationHistory + { + $this->personLocation = $personLocation; + + return $this; + } + + public function setStartDate(?DateTimeImmutable $startDate): AccompanyingPeriodLocationHistory + { + $this->startDate = $startDate; + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php index afd3ad37d..6cb34c386 100644 --- a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php +++ b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php @@ -1,5 +1,14 @@ person = $person; } - /** - * @param Address|null $previousAddress - * @return PersonAddressMoveEvent - */ - public function setPreviousAddress(?Address $previousAddress): PersonAddressMoveEvent + public function getNextAddress(): ?Address { - $this->previousAddress = $previousAddress; - return $this; + return $this->nextAddress; } + public function getNextHousehold(): ?Household + { + if (null !== $nextMembership = $this->getNextMembership()) { + return $nextMembership->getHousehold(); + } + + return null; + } + + public function getNextMembership(): ?HouseholdMember + { + return $this->nextMembership; + } public function getPerson(): Person { @@ -58,18 +75,9 @@ class PersonAddressMoveEvent extends Event return null; } - public function getNextHousehold(): ?Household + public function getPreviousMembership(): ?HouseholdMember { - if (NULL !== $nextMembership = $this->getNextMembership()) { - return $nextMembership->getHousehold(); - } - - return null; - } - - public function personChangeHousehold(): bool - { - return $this->getPreviousHousehold() !== $this->getNextHousehold(); + return $this->previousMembership; } public function personChangeAddress(): bool @@ -77,43 +85,36 @@ class PersonAddressMoveEvent extends Event return $this->getPreviousAddress() !== $this->getNextAddress(); } - /** - * @return HouseholdMember|null - */ - public function getPreviousMembership(): ?HouseholdMember + public function personChangeHousehold(): bool { - return $this->previousMembership; - } - - /** - * @param HouseholdMember|null $previousMembership - * @return PersonAddressMoveEvent - */ - public function setPreviousMembership(?HouseholdMember $previousMembership): PersonAddressMoveEvent - { - $this->previousMembership = $previousMembership; - return $this; - } - - public function getNextMembership(): ?HouseholdMember - { - return $this->nextMembership; - } - - public function setNextMembership(?HouseholdMember $nextMembership): PersonAddressMoveEvent - { - $this->nextMembership = $nextMembership; - return $this; - } - - public function getNextAddress(): ?Address - { - return $this->nextAddress; + return $this->getPreviousHousehold() !== $this->getNextHousehold(); } public function setNextAddress(?Address $nextAddress): PersonAddressMoveEvent { $this->nextAddress = $nextAddress; + + return $this; + } + + public function setNextMembership(?HouseholdMember $nextMembership): PersonAddressMoveEvent + { + $this->nextMembership = $nextMembership; + + return $this; + } + + public function setPreviousAddress(?Address $previousAddress): PersonAddressMoveEvent + { + $this->previousAddress = $previousAddress; + + return $this; + } + + public function setPreviousMembership(?HouseholdMember $previousMembership): PersonAddressMoveEvent + { + $this->previousMembership = $previousMembership; + return $this; } } diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index d3a777471..07dc9612d 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -33,6 +33,10 @@ class MembersEditor public const VALIDATION_GROUP_CREATED = 'household_memberships_created'; + private EventDispatcherInterface $eventDispatcher; + + private array $events = []; + private ?Household $household = null; private array $membershipsAffected = []; @@ -41,12 +45,8 @@ class MembersEditor private array $persistables = []; - private array $events = []; - private ValidatorInterface $validator; - private EventDispatcherInterface $eventDispatcher; - public function __construct(ValidatorInterface $validator, ?Household $household, EventDispatcherInterface $eventDispatcher) { $this->validator = $validator; diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php index 6c78d817d..1db356018 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Tests\Entity; +use ArrayIterator; +use Chill\MainBundle\Entity\Address; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; @@ -60,6 +62,62 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase $this->assertFalse($period->isClosingAfterOpening()); } + public function testHistoryLocation() + { + $period = new AccompanyingPeriod(); + $person = new Person(); + $address = new Address(); + + $period->setPersonLocation($person); + + $this->assertCount(0, $period->getLocationHistories()); + + $period->setAddressLocation($address); + $period->setPersonLocation(null); + + $this->assertCount(0, $period->getLocationHistories()); + + $period->setStep(AccompanyingPeriod::STEP_CONFIRMED); + + $this->assertCount(1, $period->getLocationHistories()); + + $this->assertSame($address, $period->getLocationHistories()->first()->getAddressLocation()); + + $period->setPersonLocation($person); + $period->setAddressLocation(null); + + $this->assertCount(2, $period->getLocationHistories()); + $this->assertSame($person, $period->getLocationHistories()->last()->getPersonLocation()); + + $period->setAddressLocation($address); + $period->setPersonLocation(null); + + $this->assertCount(3, $period->getLocationHistories()); + + $locations = $period->getLocationHistories()->toArray(); + + usort($locations, static function (AccompanyingPeriod\AccompanyingPeriodLocationHistory $a, AccompanyingPeriod\AccompanyingPeriodLocationHistory $b) { + return $a->getStartDate() <=> $b->getStartDate(); + }); + + $iterator = new ArrayIterator($locations); + $iterator->rewind(); + + do { + $current = $iterator->current(); + + $iterator->next(); + + if ($iterator->valid()) { + $next = $iterator->current(); + $this->assertNotNull($current->getEndDate()); + $this->assertEquals($current->getEndDate(), $next->getStartDate()); + } else { + $this->assertNull($current->getEndDate()); + } + } while ($iterator->valid()); + } + public function testIsClosed() { $period = new AccompanyingPeriod(new DateTime()); diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php b/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php new file mode 100644 index 000000000..4d7b9d612 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php @@ -0,0 +1,46 @@ +addSql('DROP SEQUENCE chill_person_accompanying_period_location_history_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_person_accompanying_period_location_history'); + } + + public function getDescription(): string + { + return 'Add location history to period'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE SEQUENCE chill_person_accompanying_period_location_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_person_accompanying_period_location_history (id INT NOT NULL, period_id INT DEFAULT NULL, startDate DATE NOT NULL, endDate DATE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, personLocation_id INT DEFAULT NULL, addressLocation_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_61E4E688EC8B7ADE ON chill_person_accompanying_period_location_history (period_id)'); + $this->addSql('CREATE INDEX IDX_61E4E688D5213D34 ON chill_person_accompanying_period_location_history (personLocation_id)'); + $this->addSql('CREATE INDEX IDX_61E4E6889B07D6BF ON chill_person_accompanying_period_location_history (addressLocation_id)'); + $this->addSql('CREATE INDEX IDX_61E4E6883174800F ON chill_person_accompanying_period_location_history (createdBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_location_history.startDate IS \'(DC2Type:date_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_location_history.endDate IS \'(DC2Type:date_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_location_history.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E688EC8B7ADE FOREIGN KEY (period_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E688D5213D34 FOREIGN KEY (personLocation_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E6889B07D6BF FOREIGN KEY (addressLocation_id) REFERENCES chill_main_address (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E6883174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } +} From 1658fee09024eebf65408c4bfa42bab630a14eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 15 Feb 2022 00:23:01 +0100 Subject: [PATCH 03/18] test on members editor post move + listen for event (wip) --- .../Events/PersonMoveEventSubscriber.php | 53 +++++++++++++++++++ .../Controller/HouseholdMemberController.php | 4 ++ .../ChillPersonBundle/Entity/Person.php | 2 + .../Household/MembersEditor.php | 1 + .../Household/MembersEditorFactory.php | 2 +- .../Tests/Household/MembersEditorTest.php | 51 +++++++++++++++++- .../translations/messages.fr.yml | 1 + 7 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php new file mode 100644 index 000000000..d788bd6e0 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php @@ -0,0 +1,53 @@ + 'resetPeriodLocation' + ]; + } + + public function resetPeriodLocation(PersonAddressMoveEvent $event) + { + if ($event->getPreviousAddress() !== $event->getNextAddress() + && null !== $event->getPreviousAddress() + ) { + $person = $event->getPerson(); + + foreach ($person->getCurrentAccompanyingPeriods() as $period) { + if ($period->getPersonLocation() === $person) { + $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()) + ; + + } + } + } + } + } + + +} diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php index 78234d114..849d411d7 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php @@ -180,6 +180,7 @@ class HouseholdMemberController extends ApiController public function move(Request $request, $_format): Response { try { + /** @var MembersEditor $editor */ $editor = $this->getSerializer() ->deserialize( $request->getContent(), @@ -199,6 +200,9 @@ class HouseholdMemberController extends ApiController return $this->json($errors, 422); } + // launch events on post move + $editor->postMove(); + $em = $this->getDoctrine()->getManager(); // if new household, persist it diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index e908dc489..49f7ae297 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -930,6 +930,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI /** * Get current accompanyingPeriods array. + * + * @return AccompanyingPeriod[]|array */ public function getCurrentAccompanyingPeriods(): array { diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 07dc9612d..0fad51b7c 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -22,6 +22,7 @@ use LogicException; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use function in_array; use function spl_object_hash; diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php index 366b1cb4c..7d1b19d51 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php @@ -12,8 +12,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Household; use Chill\PersonBundle\Entity\Household\Household; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class MembersEditorFactory { diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index f3e1424fb..c409cbb7b 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -14,10 +14,14 @@ namespace Chill\PersonBundle\Tests\Household; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; use Chill\PersonBundle\Household\MembersEditorFactory; use DateTimeImmutable; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use function count; /** @@ -26,13 +30,56 @@ use function count; */ final class MembersEditorTest extends TestCase { + use ProphecyTrait; + private MembersEditorFactory $factory; protected function setUp(): void { - $validator = $this->createMock(ValidatorInterface::class); + $this->factory = $this->buildMembersEditorFactory(); + } - $this->factory = new MembersEditorFactory($validator); + 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() diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 52b53553e..a52bcce60 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -547,6 +547,7 @@ period_notification: Persons are: Les usagers concernés sont les suivants Social issues are: Les problématiques sociales renseignées sont les suivantes See it online: Visualisez le parcours en ligne + Person locating period has moved: L'usager qui localise un parcours a déménagé You are getting a notification for a period which does not exists any more: Cette notification ne correspond pas à une période d'accompagnement valide. You are getting a notification for a period you are not allowed to see: La notification fait référence à une période d'accompagnement à laquelle vous n'avez pas accès. From 0a4913f341c4af87fae873b7861a0b283b69b333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 15 Feb 2022 23:52:15 +0100 Subject: [PATCH 04/18] handle event PersonMoveEvent on Period --- .../Events/PersonMoveEventSubscriber.php | 66 ++++-- .../Entity/AccompanyingPeriod.php | 16 +- .../AccompanyingPeriodParticipation.php | 1 + .../Event/Person/PersonAddressMoveEvent.php | 59 ++++- ...ation_user_on_period_has_moved.fr.txt.twig | 22 ++ .../Events/PersonMoveEventSubscriberTest.php | 205 ++++++++++++++++++ .../Person/PersonAddressMoveEventTest.php | 101 +++++++++ .../Tests/Household/MembersEditorTest.php | 88 ++++---- 8 files changed, 495 insertions(+), 63 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig create mode 100644 src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php 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 + ); + } } From 17612afd876f4018cda15adecd8e16ee202b5083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 16 Feb 2022 00:08:06 +0100 Subject: [PATCH 05/18] fix phpstan issues --- phpstan-critical.neon | 5 ----- src/Bundle/ChillPersonBundle/Household/MembersEditor.php | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/phpstan-critical.neon b/phpstan-critical.neon index 632356aa6..262d3012f 100644 --- a/phpstan-critical.neon +++ b/phpstan-critical.neon @@ -25,11 +25,6 @@ parameters: count: 1 path: src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php - - - message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Household\\\\MembersEditorFactory\\:\\:\\$validator\\.$#" - count: 2 - path: src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php - - message: "#^Variable variables are not allowed\\.$#" count: 4 diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 0fad51b7c..e327efbab 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -140,7 +140,7 @@ class MembersEditor ->matching($criteria); foreach ($participations as $participation) { - $this->events[] = $event = new PersonAddressMoveEvent(); + $this->events[] = $event = new PersonAddressMoveEvent($person); $event->setPreviousMembership($participation); $participation->setEndDate($date); $this->membershipsAffected[] = $participation; From df322d7ebbdbb8e4859b71d87ebe1b855bb0a27f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 16 Feb 2022 16:37:13 +0100 Subject: [PATCH 06/18] creation of CivilityPicker and attempt to implement -> still error --- .../Form/Type/PickCivilityType.php | 70 +++++++++++++++++++ .../ChillPersonBundle/Form/PersonType.php | 16 ++--- 2 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php new file mode 100644 index 000000000..5e357917e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php @@ -0,0 +1,70 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + /** @var QueryBuilder $qb */ + $qb = $options['query_builder']; + + $qb->where($qb->expr()->eq('at.active', ':active')) + ->orderBy('c.order'); + $qb->setParameter('active', true); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('label', 'Civility') + ->setDefault('choice_label', function (Civility $civility): string { + return $this->translatableStringHelper->localize($civility->getName()); + } + ) + ->setDefault('query_builder', function (EntityRepository $er): QueryBuilder + { + return $er->createQueryBuilder('c'); + }, + ) + ->setDefault('placeholder', 'choose civility') + ->setDefault('class', Civility::class); + + } + + public function getBlockPrefix() + { + return 'civility_type'; + } + + public function getParent() + { + return Entity::class; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index bafe21277..8634c253c 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -17,6 +17,7 @@ use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\CommentType; +use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2LanguageType; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -190,19 +191,10 @@ class PersonType extends AbstractType if ('visible' === $this->config['civility']) { $builder - ->add('civility', EntityType::class, [ - 'label' => 'Civility', - 'class' => Civility::class, - 'choice_label' => function (Civility $civility): string { - return $this->translatableStringHelper->localize($civility->getName()); - }, - 'query_builder' => static function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true') - ->orderBy('c.order'); - }, - 'placeholder' => 'choose civility', + ->add('civility', PickCivilityType::class, [ 'required' => false, + 'label' => 'Civility', + 'placeholder' => 'choose civility' ]); } From f1a9a872bb6b596b07c350896b2ece8eac132e28 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 16 Feb 2022 16:37:13 +0100 Subject: [PATCH 07/18] creation of CivilityPicker and attempt to implement -> still error --- .../Form/Type/PickCivilityType.php | 57 +++++++++++++++++++ .../ChillPersonBundle/Form/PersonType.php | 14 +---- 2 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php new file mode 100644 index 000000000..2bfecdfda --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php @@ -0,0 +1,57 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('label', 'Civility') + ->setDefault('choice_label', function (Civility $civility): string { + return $this->translatableStringHelper->localize($civility->getName()); + } + ) + ->setDefault('query_builder', function (EntityRepository $er): QueryBuilder { + return $er->createQueryBuilder('c') + ->where('c.active = true') + ->orderBy('c.order'); + }, + ) + ->setDefault('placeholder', 'choose civility') + ->setDefault('class', Civility::class); + + } + + public function getParent() + { + return Entity::class; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index bafe21277..2664edfae 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -17,6 +17,7 @@ use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\CommentType; +use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2LanguageType; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -190,18 +191,7 @@ class PersonType extends AbstractType if ('visible' === $this->config['civility']) { $builder - ->add('civility', EntityType::class, [ - 'label' => 'Civility', - 'class' => Civility::class, - 'choice_label' => function (Civility $civility): string { - return $this->translatableStringHelper->localize($civility->getName()); - }, - 'query_builder' => static function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true') - ->orderBy('c.order'); - }, - 'placeholder' => 'choose civility', + ->add('civility', PickCivilityType::class, [ 'required' => false, ]); } From 25264447bc3c5f384fae5c2e33500e17c8b0af04 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 16 Feb 2022 16:56:02 +0100 Subject: [PATCH 08/18] csfixes --- .../Form/Type/PickCivilityType.php | 16 +++++++++------- src/Bundle/ChillPersonBundle/Form/PersonType.php | 6 +----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php index 27e2618f1..592c14242 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickCivilityType.php @@ -21,7 +21,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class PickCivilityType extends AbstractType { - private TranslatableStringHelper $translatableStringHelper; public function __construct(TranslatableStringHelper $translatableStringHelper) @@ -33,23 +32,26 @@ class PickCivilityType extends AbstractType { $resolver ->setDefault('label', 'Civility') - ->setDefault('choice_label', function (Civility $civility): string { + ->setDefault( + 'choice_label', + function (Civility $civility): string { return $this->translatableStringHelper->localize($civility->getName()); - } + } ) - ->setDefault('query_builder', function (EntityRepository $er): QueryBuilder { + ->setDefault( + 'query_builder', + static function (EntityRepository $er): QueryBuilder { return $er->createQueryBuilder('c') ->where('c.active = true') ->orderBy('c.order'); - }, + }, ) ->setDefault('placeholder', 'choose civility') ->setDefault('class', Civility::class); - } public function getParent() { return EntityType::class; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index 8634c253c..3e809ad49 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Form; use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; -use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; @@ -28,9 +27,6 @@ use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonPhoneType; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; -use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\QueryBuilder; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -194,7 +190,7 @@ class PersonType extends AbstractType ->add('civility', PickCivilityType::class, [ 'required' => false, 'label' => 'Civility', - 'placeholder' => 'choose civility' + 'placeholder' => 'choose civility', ]); } From 20104f1b3b92f52917f9c90d2f169d95f425b095 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 16 Feb 2022 16:58:37 +0100 Subject: [PATCH 09/18] implementation in thirdpartyType --- .../ChillThirdPartyBundle/Form/ThirdPartyType.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php index b4ab8655d..7ca5188b1 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php @@ -11,11 +11,11 @@ declare(strict_types=1); namespace Chill\ThirdPartyBundle\Form; -use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickAddressType; use Chill\MainBundle\Form\Type\PickCenterType; +use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\ThirdPartyBundle\Entity\ThirdParty; @@ -101,16 +101,8 @@ class ThirdPartyType extends AbstractType // Contact Person ThirdParty (child) if (ThirdParty::KIND_CONTACT === $options['kind'] || ThirdParty::KIND_CHILD === $options['kind']) { $builder - ->add('civility', EntityType::class, [ + ->add('civility', PickCivilityType::class, [ 'label' => 'thirdparty.Civility', - 'class' => Civility::class, - 'choice_label' => function (Civility $civility): string { - return $this->translatableStringHelper->localize($civility->getName()); - }, - 'query_builder' => static function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true'); - }, 'placeholder' => 'thirdparty.choose civility', 'required' => false, ]) From 104af6d9b5de5e57bb77ef8156f2892fa2964c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Feb 2022 09:43:20 +0100 Subject: [PATCH 10/18] allow null value in end date in location history --- .../AccompanyingPeriod/AccompanyingPeriodLocationHistory.php | 2 +- .../ChillPersonBundle/migrations/Version20220214200327.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php index 8bef79050..9951d48c3 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php @@ -32,7 +32,7 @@ class AccompanyingPeriodLocationHistory private ?Address $addressLocation = null; /** - * @ORM\Column(type="date_immutable") + * @ORM\Column(type="date_immutable", nullable=true, options={"default":null}) */ private ?DateTimeImmutable $endDate = null; diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php b/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php index 4d7b9d612..995cb30c7 100644 --- a/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220214200327.php @@ -30,7 +30,7 @@ final class Version20220214200327 extends AbstractMigration public function up(Schema $schema): void { $this->addSql('CREATE SEQUENCE chill_person_accompanying_period_location_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE chill_person_accompanying_period_location_history (id INT NOT NULL, period_id INT DEFAULT NULL, startDate DATE NOT NULL, endDate DATE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, personLocation_id INT DEFAULT NULL, addressLocation_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE chill_person_accompanying_period_location_history (id INT NOT NULL, period_id INT DEFAULT NULL, startDate DATE NOT NULL, endDate DATE DEFAULT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, personLocation_id INT DEFAULT NULL, addressLocation_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_61E4E688EC8B7ADE ON chill_person_accompanying_period_location_history (period_id)'); $this->addSql('CREATE INDEX IDX_61E4E688D5213D34 ON chill_person_accompanying_period_location_history (personLocation_id)'); $this->addSql('CREATE INDEX IDX_61E4E6889B07D6BF ON chill_person_accompanying_period_location_history (addressLocation_id)'); @@ -42,5 +42,8 @@ final class Version20220214200327 extends AbstractMigration $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E688D5213D34 FOREIGN KEY (personLocation_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E6889B07D6BF FOREIGN KEY (addressLocation_id) REFERENCES chill_main_address (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_person_accompanying_period_location_history ADD CONSTRAINT FK_61E4E6883174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('INSERT INTO chill_person_accompanying_period_location_history (id, period_id, startDate, createdAt, personLocation_id, addresslocation_id) + SELECT nextval(\'chill_person_accompanying_period_location_history_id_seq\'), id, NOW(), NOW(), personlocation_id, addresslocation_id FROM chill_person_accompanying_period WHERE step != \'DRAFT\' + '); } } From 8675bb65c151aa0bc44209a61470bcb3f90799c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Feb 2022 10:27:44 +0100 Subject: [PATCH 11/18] PersonMoveEventSubscriber: handle case when the move is before the last locationHistory, but the move affects "now" --- phpstan-deprecations.neon | 9 ---- .../Events/PersonMoveEventSubscriber.php | 14 +++++- .../Controller/HouseholdMemberController.php | 2 +- .../Events/PersonMoveEventSubscriberTest.php | 45 +++++++++++++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/phpstan-deprecations.neon b/phpstan-deprecations.neon index 7489d4d80..7632883aa 100644 --- a/phpstan-deprecations.neon +++ b/phpstan-deprecations.neon @@ -927,15 +927,6 @@ parameters: count: 1 path: src/Bundle/ChillPersonBundle/Controller/HouseholdController.php - - - message: - """ - #^Parameter \\$translator of method Chill\\\\PersonBundle\\\\Controller\\\\HouseholdMemberController\\:\\:__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/HouseholdMemberController.php - - message: """ diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php index 053130240..a0ab44c4f 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php @@ -61,8 +61,18 @@ class PersonMoveEventSubscriber implements EventSubscriberInterface continue; } - if ($period->getPersonLocation() === $person - && $event->getMoveDate() >= $period->getLastLocationHistory()->getStartDate() + $now = new \DateTimeImmutable('now'); + + if ( + $period->getPersonLocation() === $person + && + ( + $event->getMoveDate() >= $period->getLastLocationHistory()->getStartDate() + || ( + $event->getNextAddress()->getValidFrom() < $now + && (null === $event->getNextAddress()->getValidTo() || $event->getNextAddress()->getValidTo() > $now) + ) + ) && null !== $period->getUser() && $period->getUser() !== $this->security->getUser() ) { diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php index 849d411d7..2ec72a786 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php @@ -26,7 +26,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Serializer\Exception; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use function count; diff --git a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php index 0ba8cf878..5948e6471 100644 --- a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php @@ -80,6 +80,51 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $eventSubscriber->resetPeriodLocation($event); } + public function testEventChangeHouseholdNotificationForPeriodChangeLocationOfPersonAnteriorToCurrentLocationHistory() + { + $person = new Person(); + $period = new AccompanyingPeriod(); + $period + ->setStep(AccompanyingPeriod::STEP_CONFIRMED) + ->setPersonLocation($person) + ->setUser(new User()) + ->addPerson($person); + $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('1 month ago')) + ); + $nextMembership = new HouseholdMember(); + $nextMembership + ->setPerson($person) + ->setHousehold($nextHousehold) + ->setStartDate(new DateTimeImmutable('1 month ago')); + + $event = new PersonAddressMoveEvent($person); + $event + ->setPreviousMembership($previousMembership) + ->setNextMembership($nextMembership); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->persist(Argument::type(Notification::class))->shouldBeCalled(1); + $eventSubscriber = $this->buildSubscriber(null, $em->reveal(), null, null); + + $eventSubscriber->resetPeriodLocation($event); + + $this->assertSame($previousAddress, $period->getAddressLocation()); + $this->assertNull($period->getPersonLocation()); + } + public function testEventChangeHouseholdNotification() { $person = new Person(); From caa63ea97aa365d9435f2c631d0ee411d23b10af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Feb 2022 14:04:51 +0100 Subject: [PATCH 12/18] PersonAddressMoveEvent on household move (wip) --- phpstan-deprecations.neon | 9 --- src/Bundle/ChillMainBundle/Entity/Address.php | 5 +- .../Events/PersonMoveEventSubscriber.php | 3 +- .../Controller/HouseholdApiController.php | 55 +++++++++++++- .../Controller/HouseholdController.php | 2 +- .../ChillPersonExtension.php | 9 --- .../AccompanyingPeriodLocationHistory.php | 3 +- .../Entity/Household/Household.php | 72 ++++++++++++++++--- .../Events/PersonMoveEventSubscriberTest.php | 48 ++++++++++++- .../Tests/Entity/Household/HouseholdTest.php | 41 +++++++++++ .../Person/PersonAddressMoveEventTest.php | 38 +++++++--- 11 files changed, 237 insertions(+), 48 deletions(-) diff --git a/phpstan-deprecations.neon b/phpstan-deprecations.neon index 7632883aa..694825904 100644 --- a/phpstan-deprecations.neon +++ b/phpstan-deprecations.neon @@ -918,15 +918,6 @@ parameters: count: 1 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: """ diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 4ccafa8c2..69acf2670 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -142,7 +142,7 @@ class Address * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") * @Groups({"write"}) */ - private ?PostalCode $postcode; + private ?PostalCode $postcode = null; /** * @var string|null @@ -305,9 +305,8 @@ class Address /** * Get postcode. * - * @return PostalCode */ - public function getPostcode() + public function getPostcode(): ?PostalCode { return $this->postcode; } diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php index a0ab44c4f..6f2ed6654 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\AccompanyingPeriod\Events; +use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Notification; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; @@ -78,7 +79,7 @@ class PersonMoveEventSubscriber implements EventSubscriberInterface ) { // reset the location, back to an address $period->setPersonLocation(null); - $period->setAddressLocation($event->getPreviousAddress()); + $period->setAddressLocation(Address::createFromAddress($event->getPreviousAddress())); $notification = new Notification(); $notification diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php index 1ebde2b57..a48bf7a3d 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php @@ -16,15 +16,20 @@ use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\AddressReference; use Chill\MainBundle\Serializer\Model\Collection; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface; use Chill\PersonBundle\Repository\Household\HouseholdRepository; +use Chill\PersonBundle\Security\Authorization\HouseholdVoter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use function array_filter; use function array_values; @@ -34,10 +39,14 @@ class HouseholdApiController extends ApiController private HouseholdRepository $householdRepository; + private EventDispatcherInterface $eventDispatcher; + public function __construct( + EventDispatcherInterface $eventDispatcher, HouseholdRepository $householdRepository, HouseholdACLAwareRepositoryInterface $householdACLAwareRepository ) { + $this->eventDispatcher = $eventDispatcher; $this->householdRepository = $householdRepository; $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"]] + ); } /** diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index ba4b788d7..07fa34b5a 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -24,7 +24,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use function array_key_exists; use function count; diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 01df460e3..716ef9ee8 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -527,15 +527,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 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' => [ 'path' => '/suggest/by-person/{person_id}/through-accompanying-period-participation.{_format}', 'methods' => [ diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php index 9951d48c3..ec6043c30 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; use Chill\MainBundle\Entity\Address; use Chill\PersonBundle\Entity\AccompanyingPeriod; @@ -22,7 +23,7 @@ use Doctrine\ORM\Mapping as ORM; * @ORM\Entity * @ORM\Table("chill_person_accompanying_period_location_history") */ -class AccompanyingPeriodLocationHistory +class AccompanyingPeriodLocationHistory implements TrackCreationInterface { use TrackCreationTrait; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index ef718bc2e..7c352e99a 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -106,20 +106,74 @@ class Household $this->compositions = new ArrayCollection(); } - /** - * @return $this - */ - public function addAddress(Address $address) + public function addAddress(Address $address): self { - foreach ($this->getAddresses() as $a) { - if ($a->getValidFrom() <= $address->getValidFrom() && $a->getValidTo() === null) { - $a->setValidTo($address->getValidFrom()); + if (!$this->addresses->contains($address)) { + $this->addresses[] = $address; + $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 diff --git a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php index 5948e6471..608e4885c 100644 --- a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php @@ -121,7 +121,7 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $eventSubscriber->resetPeriodLocation($event); - $this->assertSame($previousAddress, $period->getAddressLocation()); + $this->assertNotNull($period->getAddressLocation()); $this->assertNull($period->getPersonLocation()); } @@ -166,7 +166,49 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $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()); } @@ -201,7 +243,7 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $eventSubscriber->resetPeriodLocation($event); - $this->assertSame($previousAddress, $period->getAddressLocation()); + $this->assertNotNull($period->getAddressLocation()); $this->assertNull($period->getPersonLocation()); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php index f63c12a2c..6a60267df 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Entity\Household; +use Chill\MainBundle\Entity\Address; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdComposition; use DateTimeImmutable; +use DateTime; 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'), $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()); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php index 5347f8c68..303b5840e 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php @@ -30,11 +30,6 @@ 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')) @@ -71,11 +66,6 @@ final class PersonAddressMoveEventTest extends TestCase 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')) @@ -98,4 +88,32 @@ final class PersonAddressMoveEventTest extends TestCase $this->assertTrue($event->personLeaveWithoutHousehold()); $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()); + } } From 4f4b1bfbaabd711ad0f12dd94a79f9cee6df004e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Feb 2022 00:12:57 +0100 Subject: [PATCH 13/18] PersonAddressMove: fix tests, address history on household, and household::getMembersOnRange --- src/Bundle/ChillMainBundle/Entity/Address.php | 1 - ...p => PersonAddressMoveEventSubscriber.php} | 13 +- .../Controller/HouseholdApiController.php | 24 +-- .../AccompanyingPeriodLocationHistory.php | 4 +- .../Entity/Household/Household.php | 152 +++++++++--------- .../Event/Person/PersonAddressMoveEvent.php | 38 +++++ .../Events/PersonMoveEventSubscriberTest.php | 130 ++++++++------- .../Tests/Entity/Household/HouseholdTest.php | 79 +++++++-- .../Person/PersonAddressMoveEventTest.php | 57 ++++--- 9 files changed, 288 insertions(+), 210 deletions(-) rename src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/{PersonMoveEventSubscriber.php => PersonAddressMoveEventSubscriber.php} (87%) diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 69acf2670..8eac6668b 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -304,7 +304,6 @@ class Address /** * Get postcode. - * */ public function getPostcode(): ?PostalCode { diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonAddressMoveEventSubscriber.php similarity index 87% rename from src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php rename to src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonAddressMoveEventSubscriber.php index 6f2ed6654..3eff58dc7 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonMoveEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/PersonAddressMoveEventSubscriber.php @@ -15,13 +15,14 @@ use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Notification; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; +use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Security; use Symfony\Component\Templating\EngineInterface; use Symfony\Contracts\Translation\TranslatorInterface; -class PersonMoveEventSubscriber implements EventSubscriberInterface +class PersonAddressMoveEventSubscriber implements EventSubscriberInterface { private EngineInterface $engine; @@ -62,17 +63,11 @@ class PersonMoveEventSubscriber implements EventSubscriberInterface continue; } - $now = new \DateTimeImmutable('now'); - if ( $period->getPersonLocation() === $person - && - ( + && ( $event->getMoveDate() >= $period->getLastLocationHistory()->getStartDate() - || ( - $event->getNextAddress()->getValidFrom() < $now - && (null === $event->getNextAddress()->getValidTo() || $event->getNextAddress()->getValidTo() > $now) - ) + || $event->willChangeBeActiveAt(new DateTimeImmutable('now')) ) && null !== $period->getUser() && $period->getUser() !== $this->security->getUser() diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php index a48bf7a3d..470094507 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php @@ -22,25 +22,25 @@ use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; use Chill\PersonBundle\Repository\Household\HouseholdACLAwareRepositoryInterface; use Chill\PersonBundle\Repository\Household\HouseholdRepository; use Chill\PersonBundle\Security\Authorization\HouseholdVoter; +use DateTimeImmutable; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; -use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; 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_values; class HouseholdApiController extends ApiController { + private EventDispatcherInterface $eventDispatcher; + private HouseholdACLAwareRepositoryInterface $householdACLAwareRepository; private HouseholdRepository $householdRepository; - private EventDispatcherInterface $eventDispatcher; - public function __construct( EventDispatcherInterface $eventDispatcher, HouseholdRepository $householdRepository, @@ -76,10 +76,10 @@ class HouseholdApiController extends ApiController } /** - * Add an address to a household + * 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"}) + * methods={"POST"}, requirements={"_format": "json"}) */ public function householdAddressApi(Household $household, Request $request, string $_format): Response { @@ -87,22 +87,22 @@ class HouseholdApiController extends ApiController /** @var Address $address */ $address = $this->getSerializer()->deserialize($request->getContent(), Address::class, $_format, [ - AbstractNormalizer::GROUPS => ['write'] + AbstractNormalizer::GROUPS => ['write'], ]); $household->addAddress($address); foreach ($household->getMembersOnRange( - \DateTimeImmutable::createFromMutable($address->getValidFrom()), + DateTimeImmutable::createFromMutable($address->getValidFrom()), null === $address->getValidTo() ? null : - \DateTimeImmutable::createFromMutable($address->getValidTo()) + DateTimeImmutable::createFromMutable($address->getValidTo()) ) as $member) { /** @var HouseholdMember $member */ $event = new PersonAddressMoveEvent($member->getPerson()); $event ->setPreviousAddress($household->getPreviousAddressOf($address)) - ->setNextAddress($address) - ; + ->setNextAddress($address); + dump($event); $this->eventDispatcher->dispatch($event); } @@ -118,7 +118,7 @@ class HouseholdApiController extends ApiController $address, Response::HTTP_OK, [], - [AbstractNormalizer::GROUPS => ["read"]] + [AbstractNormalizer::GROUPS => ['read']] ); } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php index ec6043c30..1dcb5a1bf 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodLocationHistory.php @@ -28,12 +28,12 @@ class AccompanyingPeriodLocationHistory implements TrackCreationInterface use TrackCreationTrait; /** - * @ORM\ManyToOne(targetEntity=Address::class) + * @ORM\ManyToOne(targetEntity=Address::class, cascade={"persist"}) */ private ?Address $addressLocation = null; /** - * @ORM\Column(type="date_immutable", nullable=true, options={"default":null}) + * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) */ private ?DateTimeImmutable $endDate = null; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index 7c352e99a..b922ad19c 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -116,66 +116,6 @@ class Household 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; - } - } - } - - return null; - } - - 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 { if (!$this->compositions->contains($composition)) { @@ -211,6 +151,31 @@ class Household return $this->addresses; } + public function getAddressesOrdered(): array + { + $addresses = $this->getAddresses()->toArray(); + usort($addresses, static 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 getCommentMembers(): CommentEmbeddable { return $this->commentMembers; @@ -412,24 +377,25 @@ class Household public function getMembersOnRange(DateTimeImmutable $from, ?DateTimeImmutable $to): Collection { - $criteria = new Criteria(); - $expr = Criteria::expr(); + return $this->getMembers()->filter(static function (HouseholdMember $m) use ($from, $to) { + if (null === $m->getEndDate() && null !== $to) { + return $m->getStartDate() <= $to; + } - $criteria->where( - $expr->gte('startDate', $from) - ); + if (null === $to) { + return $m->getStartDate() >= $from || null === $m->getEndDate(); + } - if (null !== $to) { - $criteria->andWhere( - $expr->orX( - $expr->lte('endDate', $to), - $expr->eq('endDate', null) - ), - ); - } + if (null !== $m->getEndDate() && $m->getEndDate() < $from) { + return false; + } - return $this->getMembers() - ->matching($criteria); + if ($m->getStartDate() <= $to) { + return true; + } + + return false; + }); } public function getNonCurrentMembers(?DateTimeImmutable $now = null): Collection @@ -472,6 +438,25 @@ class Household return $this->getNonCurrentMembers($now)->matching($criteria); } + 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 ($iterator->current() === $address) { + return $current; + } + } + } + + return null; + } + public function getWaitingForBirth(): bool { return $this->waitingForBirth; @@ -516,6 +501,23 @@ class Household } while ($iterator->valid()); } + 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 removeAddress(Address $address) { $this->addresses->removeElement($address); diff --git a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php index 9be02854c..2f4a1a92e 100644 --- a/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php +++ b/src/Bundle/ChillPersonBundle/Event/Person/PersonAddressMoveEvent.php @@ -168,4 +168,42 @@ class PersonAddressMoveEvent extends Event return $this; } + + /** + * Will the change affect this date ? + */ + public function willChangeBeActiveAt(DateTimeImmutable $date): bool + { + if ($this->getMoveDate() < $date && $this->personLeaveWithoutHousehold()) { + return true; + } + + if ($this->personChangeHousehold()) { + if ($this->getMoveDate() > $date) { + return false; + } + + if (null === $this->getNextMembership()->getEndDate()) { + return true; + } + + if ($this->getNextMembership()->getEndDate() > $date) { + return true; + } + } else { + if ($this->getNextAddress()->getValidFrom() > $date) { + return false; + } + + if (null === $this->getNextAddress()->getValidTo()) { + return true; + } + + if ($this->getNextAddress()->getValidTo() > $date) { + return true; + } + } + + return false; + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php index 608e4885c..321d69aac 100644 --- a/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/AccompanyingPeriod/Events/PersonMoveEventSubscriberTest.php @@ -14,7 +14,7 @@ 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\AccompanyingPeriod\Events\PersonAddressMoveEventSubscriber; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdMember; @@ -80,51 +80,6 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $eventSubscriber->resetPeriodLocation($event); } - public function testEventChangeHouseholdNotificationForPeriodChangeLocationOfPersonAnteriorToCurrentLocationHistory() - { - $person = new Person(); - $period = new AccompanyingPeriod(); - $period - ->setStep(AccompanyingPeriod::STEP_CONFIRMED) - ->setPersonLocation($person) - ->setUser(new User()) - ->addPerson($person); - $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('1 month ago')) - ); - $nextMembership = new HouseholdMember(); - $nextMembership - ->setPerson($person) - ->setHousehold($nextHousehold) - ->setStartDate(new DateTimeImmutable('1 month ago')); - - $event = new PersonAddressMoveEvent($person); - $event - ->setPreviousMembership($previousMembership) - ->setNextMembership($nextMembership); - - $em = $this->prophesize(EntityManagerInterface::class); - $em->persist(Argument::type(Notification::class))->shouldBeCalled(1); - $eventSubscriber = $this->buildSubscriber(null, $em->reveal(), null, null); - - $eventSubscriber->resetPeriodLocation($event); - - $this->assertNotNull($period->getAddressLocation()); - $this->assertNull($period->getPersonLocation()); - } - public function testEventChangeHouseholdNotification() { $person = new Person(); @@ -170,40 +125,43 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $this->assertNull($period->getPersonLocation()); } - public function testEventPersonChangeAddressInThePast() + public function testEventChangeHouseholdNotificationForPeriodChangeLocationOfPersonAnteriorToCurrentLocationHistory() { $person = new Person(); $period = new AccompanyingPeriod(); $period ->setStep(AccompanyingPeriod::STEP_CONFIRMED) ->setPersonLocation($person) - ->addPerson($person) - ->setUser(new User()); + ->setUser(new User()) + ->addPerson($person); $this->forceIdToPeriod($period); - $membership = new HouseholdMember(); - $membership + $previousHousehold = (new Household())->addAddress( + ($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago')) + ); + $previousMembership = new HouseholdMember(); + $previousMembership ->setPerson($person) - ->setHousehold($household = new Household()) + ->setHousehold($previousHousehold) ->setStartDate(new DateTimeImmutable('1 year ago')) - ; + ->setEndDate(new DateTimeImmutable('tomorrow')); - $previousAddress = new Address(); - $previousAddress->setValidFrom(new DateTime('6 months ago')); - $household->addAddress($previousAddress); - - $newAddress = new Address(); - $newAddress->setValidFrom(new DateTime('tomorrow')); - $household->addAddress($newAddress); + $nextHousehold = (new Household())->addAddress( + (new Address())->setValidFrom(new DateTime('1 month ago')) + ); + $nextMembership = new HouseholdMember(); + $nextMembership + ->setPerson($person) + ->setHousehold($nextHousehold) + ->setStartDate(new DateTimeImmutable('1 month ago')); $event = new PersonAddressMoveEvent($person); $event - ->setPreviousAddress($household->getPreviousAddressOf($newAddress)) - ->setNextAddress($newAddress) - ; + ->setPreviousMembership($previousMembership) + ->setNextMembership($nextMembership); $em = $this->prophesize(EntityManagerInterface::class); - $em->persist(Argument::type(Notification::class))->shouldBeCalledTimes(1); + $em->persist(Argument::type(Notification::class))->shouldBeCalled(1); $eventSubscriber = $this->buildSubscriber(null, $em->reveal(), null, null); $eventSubscriber->resetPeriodLocation($event); @@ -247,12 +205,52 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $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()); + } + private function buildSubscriber( ?EngineInterface $engine = null, ?EntityManagerInterface $entityManager = null, ?Security $security = null, ?TranslatorInterface $translator = null - ): PersonMoveEventSubscriber { + ): PersonAddressMoveEventSubscriber { if (null === $translator) { $double = $this->prophesize(TranslatorInterface::class); $translator = $double->reveal(); @@ -274,7 +272,7 @@ final class PersonMoveEventSubscriberTest extends KernelTestCase $entityManager = $double->reveal(); } - return new PersonMoveEventSubscriber( + return new PersonAddressMoveEventSubscriber( $engine, $entityManager, $security, diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php index 6a60267df..6799f882b 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php @@ -14,8 +14,10 @@ namespace Entity\Household; use Chill\MainBundle\Entity\Address; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\HouseholdComposition; -use DateTimeImmutable; +use Chill\PersonBundle\Entity\Household\HouseholdMember; +use Chill\PersonBundle\Entity\Person; use DateTime; +use DateTimeImmutable; use PHPUnit\Framework\TestCase; /** @@ -24,27 +26,47 @@ use PHPUnit\Framework\TestCase; */ final class HouseholdTest extends TestCase { - public function testHouseholdComposition() + public function testGetMembersOnRange() { $household = new Household(); - $household->addComposition(($first = new HouseholdComposition()) - ->setStartDate(new DateTimeImmutable('2021-12-01'))); + $household->addMember($householdMemberA = (new HouseholdMember()) + ->setStartDate(new DateTimeImmutable('2020-01-01')) + ->setEndDate(new DateTimeImmutable('2020-12-31')) + ->setPerson(new Person())); + $household->addMember($householdMemberB = (new HouseholdMember()) + ->setStartDate(new DateTimeImmutable('2020-06-01')) + ->setEndDate(new DateTimeImmutable('2021-06-31')) + ->setPerson(new Person())); + $household->addMember($householdMemberC = (new HouseholdMember()) + ->setStartDate(new DateTimeImmutable('2021-01-01')) + ->setEndDate(null) + ->setPerson(new Person())); - $this->assertNull($first->getEndDate()); + $members = $household->getMembersOnRange(new DateTimeImmutable('2019-01-01'), null); - $household->addComposition(($second = new HouseholdComposition()) - ->setStartDate(new DateTimeImmutable('2021-12-31'))); + $this->assertCount(3, $members); + $this->assertContains($householdMemberA, $members); + $this->assertContains($householdMemberB, $members); + $this->assertContains($householdMemberC, $members); - $this->assertEquals(new DateTimeImmutable('2021-12-31'), $first->getEndDate()); - $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); + $members = $household->getMembersOnRange(new DateTimeImmutable('2020-01-01'), new DateTimeImmutable('2020-07-01')); + $this->assertCount(2, $members); + $this->assertContains($householdMemberA, $members); + $this->assertContains($householdMemberB, $members); + $this->assertNotContains($householdMemberC, $members); - $household->addComposition(($inside = new HouseholdComposition()) - ->setStartDate(new DateTimeImmutable('2021-12-15'))); + $members = $household->getMembersOnRange(new DateTimeImmutable('2020-01-01'), new DateTimeImmutable('2022-12-31')); + $this->assertCount(3, $members); + $this->assertContains($householdMemberA, $members); + $this->assertContains($householdMemberB, $members); + $this->assertContains($householdMemberC, $members); - $this->assertEquals(new DateTimeImmutable('2021-12-15'), $first->getEndDate()); - $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); - $this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate()); + $members = $household->getMembersOnRange(new DateTimeImmutable('2021-01-01'), new DateTimeImmutable('2022-12-31')); + $this->assertCount(2, $members); + $this->assertNotContains($householdMemberA, $members); + $this->assertContains($householdMemberB, $members); + $this->assertContains($householdMemberC, $members); } public function testHouseholdAddressConsistent() @@ -72,7 +94,7 @@ final class HouseholdTest extends TestCase $this->assertNull($lastAddress->getValidTo()); $futureAddress = new Address(); - $futureAddress->setValidFrom(new DateTime('tomorrow')); + $futureAddress->setValidFrom($tomorrow = new DateTime('tomorrow')); $household->addAddress($futureAddress); $addresses = $household->getAddressesOrdered(); @@ -83,6 +105,31 @@ final class HouseholdTest extends TestCase $this->assertEquals($oneMonthAgo, $previousAddress->getValidFrom()); $this->assertEquals($yesterday, $previousAddress->getValidTo()); $this->assertEquals($yesterday, $lastAddress->getValidFrom()); - $this->assertNull($lastAddress->getValidTo()); + $this->assertEquals($tomorrow, $lastAddress->getValidTo()); + $this->assertEquals($tomorrow, $futureAddress->getValidFrom()); + $this->assertNull($futureAddress->getValidTo()); + } + + public function testHouseholdComposition() + { + $household = new Household(); + + $household->addComposition(($first = new HouseholdComposition()) + ->setStartDate(new DateTimeImmutable('2021-12-01'))); + + $this->assertNull($first->getEndDate()); + + $household->addComposition(($second = new HouseholdComposition()) + ->setStartDate(new DateTimeImmutable('2021-12-31'))); + + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $first->getEndDate()); + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); + + $household->addComposition(($inside = new HouseholdComposition()) + ->setStartDate(new DateTimeImmutable('2021-12-15'))); + + $this->assertEquals(new DateTimeImmutable('2021-12-15'), $first->getEndDate()); + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate()); + $this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate()); } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php index 303b5840e..cb83fa734 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php @@ -12,7 +12,6 @@ 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; @@ -27,6 +26,34 @@ use PHPUnit\Framework\TestCase; */ final class PersonAddressMoveEventTest extends TestCase { + public function testPersonChangeAddress() + { + $person = new Person(); + + $household = (new Household())->addAddress( + ($previousAddress = new Address())->setValidFrom(new DateTime('1 year ago')) + ); + $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()); + } + public function testPersonChangeHousehold() { $person = new Person(); @@ -88,32 +115,4 @@ final class PersonAddressMoveEventTest extends TestCase $this->assertTrue($event->personLeaveWithoutHousehold()); $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()); - } } From 610b86134573f59cec137e8b603658314799c2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Feb 2022 12:44:43 +0100 Subject: [PATCH 14/18] fix test on PersonAddressMoveEvent::personChangeAddress --- .../Tests/Event/Person/PersonAddressMoveEventTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php index cb83fa734..5560de209 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php @@ -50,8 +50,8 @@ final class PersonAddressMoveEventTest extends TestCase $this->assertSame($previousAddress, $event->getPreviousAddress()); $this->assertSame($nextAddress, $event->getNextAddress()); - $this->assertEquals(new DateTime('tomorrow'), $nextAddress->getValidFrom()); - $this->assertEquals(new DateTime('tomorrow'), $event->getMoveDate()); + $this->assertEquals(new DateTime('1 month ago'), $nextAddress->getValidFrom()); + $this->assertEquals(new DateTime('1 month ago'), $event->getMoveDate()); } public function testPersonChangeHousehold() From 4e9879ba9286e5481c2abadc1a8da4d20f86df62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Feb 2022 12:49:19 +0100 Subject: [PATCH 15/18] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3ab19ad..53f2a855a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to * [Household]: Add end date in HouseholdMember form for 'enfant hors menage' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/434) * [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435) * [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481) +* [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address. ## Test releases From 213da59b0bb5034ad6ca1dc65589faffe865710b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Feb 2022 13:22:35 +0100 Subject: [PATCH 16/18] fix test failing due to microseconds difference --- .../Tests/Event/Person/PersonAddressMoveEventTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php index 5560de209..65d10f68a 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Event/Person/PersonAddressMoveEventTest.php @@ -50,8 +50,8 @@ final class PersonAddressMoveEventTest extends TestCase $this->assertSame($previousAddress, $event->getPreviousAddress()); $this->assertSame($nextAddress, $event->getNextAddress()); - $this->assertEquals(new DateTime('1 month ago'), $nextAddress->getValidFrom()); - $this->assertEquals(new DateTime('1 month ago'), $event->getMoveDate()); + $this->assertEquals((new DateTime('1 month ago'))->format('Y-m-d'), $nextAddress->getValidFrom()->format('Y-m-d')); + $this->assertEquals((new DateTime('1 month ago'))->format('Y-m-d'), $event->getMoveDate()->format('Y-m-d')); } public function testPersonChangeHousehold() From 0a2e2301a945d8d97292d7ca680f16f729a17017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Feb 2022 15:25:46 +0100 Subject: [PATCH 17/18] document ThirdParty type and categories [ci-skip] --- .../Entity/ThirdParty.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 390427d74..e8f7e292f 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -45,6 +45,40 @@ use function spl_object_hash; * all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this * center. * + * A ThirdParty may have: + * + * * 0, one or more categories; + * * 0, one or more type + * * 1 kind. + * + * ## Kind + * + * The kind indicate if a thirdparty is a: + * + * * company ("personne morale"); + * * a contact ("personne morale") + * * a child inside a company ("contact" in French). Only companies may have childs + * + * **take care** the french translation may lead to misinterpretation, as the string "contact" is the translation of + * kind "child". + * + * ## Categories and types + * + * ThirdParty may have zero, one or more categories and/or type. + * + * Categories are managed in the database. The Chill administrator may create or desactivate categories. + * + * Types are also a way to categorize the thirdparties, but this is done **into the code**, through + * @link{Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeProviderInterface}. The type is stored into the + * database by a Php array, mapped by a jsonb into the database. This has one advantage: it is easily searchable. + * + * As the list of type is hardcoded into database, it is more easily searchable. (for chill 2.0, the + * @link{Chill\ThirdPartyBundle\Form\Type\PickThirdPartyDynamicType} does not support it yet, but + * the legacy @link{Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType} does. + * + * The difference between categories and types is transparent for user: they choose the same fields into the UI, without + * noticing a difference. + * * @ORM\Entity * @ORM\Table(name="chill_3party.third_party") * @DiscriminatorMap(typeProperty="type", mapping={ From 8d74566eacd0ae848fb0aa0ec6eca50baad7c1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Feb 2022 17:20:15 +0100 Subject: [PATCH 18/18] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f2a855a..63f949174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to * [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435) * [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481) * [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address. +* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType ## Test releases