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'); + } +}