From 74520330b1d9555a9ce3c1d26244576b2986d617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 13:25:37 +0200 Subject: [PATCH 1/8] add position for household and prefix tables names --- .../migrations/Version20210528090000.php | 29 ++++++ .../Entity/Household/Household.php | 3 + .../Entity/Household/HouseholdMembers.php | 7 +- .../Entity/Household/Position.php | 93 +++++++++++++++++++ src/Bundle/ChillPersonBundle/Household | 0 .../Household/PositionRepository.php | 50 ++++++++++ .../migrations/Version20210528092625.php | 62 +++++++++++++ .../migrations/Version20210528111624.php | 39 ++++++++ 8 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20210528090000.php create mode 100644 src/Bundle/ChillPersonBundle/Entity/Household/Position.php create mode 100644 src/Bundle/ChillPersonBundle/Household create mode 100644 src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php new file mode 100644 index 000000000..de3c7336a --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php @@ -0,0 +1,29 @@ +addSql('CREATE EXTENSION IF NOT EXISTS btree_gist'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP EXTENSION btree_gist'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index 4e8787a96..420325d6d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -8,6 +8,9 @@ use Chill\MainBundle\Entity\Address; /** * @ORM\Entity + * @ORM\Table( + * name="chill_person_household" + * ) */ class Household { diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php index 5d16649a8..d39034e32 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php @@ -5,9 +5,14 @@ namespace Chill\PersonBundle\Entity\Household; use Doctrine\ORM\Mapping as ORM; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\Position; + /** * @ORM\Entity + * @ORM\Table( + * name="chill_person_household_members" + * ) */ class HouseholdMembers { @@ -19,7 +24,7 @@ class HouseholdMembers private $id; /** - * @ORM\Column(type="string", length=255, nullable=true) + * @ORM\ManyToOne(targetEntity=Position::class) */ private $position; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php new file mode 100644 index 000000000..60e19b25a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -0,0 +1,93 @@ +id; + } + + public function getLabel(): ?array + { + return $this->label; + } + + public function setLabel(array $label): self + { + $this->label = $label; + + return $this; + } + + public function getShareHouseHold(): ?bool + { + return $this->shareHouseHold; + } + + public function setShareHouseHold(bool $shareHouseHold): self + { + $this->shareHouseHold = $shareHouseHold; + + return $this; + } + + public function getAllowHolder(): ?bool + { + return $this->allowHolder; + } + + public function setAllowHolder(bool $allowHolder): self + { + $this->allowHolder = $allowHolder; + + return $this; + } + + public function getOrdering(): ?float + { + return $this->ordering; + } + + public function setOrdering(float $ordering): self + { + $this->ordering = $ordering; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Household b/src/Bundle/ChillPersonBundle/Household new file mode 100644 index 000000000..e69de29bb diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php new file mode 100644 index 000000000..0ca9ef10e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('p.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Position + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php new file mode 100644 index 000000000..5d1909424 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php @@ -0,0 +1,62 @@ +addSql('ALTER TABLE householdmembers DROP CONSTRAINT fk_4d1fb288e79ff843'); + $this->addSql('ALTER TABLE householdmembers DROP CONSTRAINT fk_4d1fb288217bbb47'); + + // rename tables + $this->addSql('ALTER TABLE householdmembers RENAME TO chill_person_household_members'); + $this->addSql('ALTER TABLE household RENAME TO chill_person_household'); + + // rename sequences + $this->addSql('ALTER SEQUENCE household_id_seq RENAME TO chill_person_household_id_seq'); + $this->addSql('ALTER SEQUENCE householdmembers_id_seq RENAME TO chill_person_household_members_id_seq'); + + // recreate constraints + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT FK_EEF5DED7217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT FK_EEF5DED7E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + + // create constraint 'householdmembers not overlaps' + $this->addSql('ALTER TABLE chill_person_household_members ADD CHECK (startdate < enddate)'); + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT '. + "household_members_not_overlaps EXCLUDE USING GIST( + -- extension btree_gist required to include comparaison with integer + person_id WITH =, + daterange(startdate, enddate) WITH && + ) WHERE (sharedhousehold IS TRUE)"); + + // rename constraints + $this->addSql('ALTER TABLE public.chill_person_household_to_addresses DROP CONSTRAINT fk_7109483e79ff843'); + $this->addSql('ALTER TABLE chill_person_household_to_addresses ADD CONSTRAINT FK_C28AF063E79FF843 FOREIGN KEY (household_id) REFERENCES chill_person_household (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + + // rename indexes + $this->addSql('ALTER INDEX idx_7109483e79ff843 RENAME TO IDX_C28AF063E79FF843'); + $this->addSql('ALTER INDEX idx_7109483f5b7af75 RENAME TO IDX_C28AF063F5B7AF75'); + $this->addSql('ALTER INDEX idx_4d1fb288e79ff843 RENAME TO IDX_EEF5DED7E79FF843'); + $this->addSql('ALTER INDEX idx_4d1fb288217bbb47 RENAME TO IDX_EEF5DED7217BBB47'); + } + + public function down(Schema $schema): void + { + $this->throwIrreversibleMigrationException("the down method is not implemented"); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php new file mode 100644 index 000000000..2228d42c3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528111624.php @@ -0,0 +1,39 @@ +addSql('CREATE SEQUENCE chill_person_household_position_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_person_household_position (id INT NOT NULL, label JSON NOT NULL, shareHouseHold BOOLEAN NOT NULL, allowHolder BOOLEAN NOT NULL, ordering DOUBLE PRECISION NOT NULL, PRIMARY KEY(id))'); + + $this->addSql('ALTER TABLE chill_person_household_members ADD position_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members DROP "position"'); + $this->addSql('ALTER TABLE chill_person_household_members ADD CONSTRAINT FK_EEF5DED7DD842E46 FOREIGN KEY (position_id) REFERENCES chill_person_household_position (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_EEF5DED7DD842E46 ON chill_person_household_members (position_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_household_members DROP CONSTRAINT FK_EEF5DED7DD842E46'); + $this->addSql('DROP SEQUENCE chill_person_household_position_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_person_household_position'); + $this->addSql('ALTER TABLE chill_person_household_members ADD "position" VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members DROP position_id'); + } +} From 94bcbac06a64847be6da84e17fafcba9dd7b21f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 13:55:06 +0200 Subject: [PATCH 2/8] add extension btree_gist to postgres --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86e65c390..6973ff6d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ cache: before_script: # add extensions to postgres - - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm;" + - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gist;" # Install and run Composer - curl -sS https://getcomposer.org/installer | php - php -d memory_limit=2G composer.phar install From 87ba68971c5bdec1274762ec4b60ae05cdc25055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 16:41:37 +0200 Subject: [PATCH 3/8] first impl of person Mover + add fixtures --- .../DataFixtures/ORM/LoadHousehold.php | 101 ++++++++++++++++++ .../ORM/LoadHouseholdPosition.php | 38 +++++++ .../ChillPersonExtension.php | 1 + ...useholdMembers.php => HouseholdMember.php} | 68 ++++++++---- .../Entity/Household/Position.php | 4 +- .../ChillPersonBundle/Entity/Person.php | 22 ++++ src/Bundle/ChillPersonBundle/Household | 0 .../Household/MoveMembers.php | 80 ++++++++++++++ .../Household/MoveMembersFactory.php | 27 +++++ .../config/services/fixtures.yaml | 1 + .../config/services/household.yaml | 3 + .../migrations/Version20210528132405.php | 31 ++++++ .../migrations/Version20210528142121.php | 29 +++++ 13 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php create mode 100644 src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php rename src/Bundle/ChillPersonBundle/Entity/Household/{HouseholdMembers.php => HouseholdMember.php} (60%) delete mode 100644 src/Bundle/ChillPersonBundle/Household create mode 100644 src/Bundle/ChillPersonBundle/Household/MoveMembers.php create mode 100644 src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php create mode 100644 src/Bundle/ChillPersonBundle/config/services/household.yaml create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php new file mode 100644 index 000000000..a8c3c73be --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php @@ -0,0 +1,101 @@ +movementFactory = $movementFactory; + $this->em = $em; + } + + public function load(ObjectManager $manager) + { + $this->preparePersonIds(); + + for ($i=0; $i < self::NUMBER_OF_HOUSEHOLD; $i++) { + $household = new Household(); + $manager->persist($household); + + $movement = $this->movementFactory->createMovement($household); + + // load adults + $k = 0; + foreach ($this->getRandomPersons(1, 3) as $person) { + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $position = $this->getReference(LoadHouseholdPosition::ADULT); + + $movement->addMovement($date, $person, $position, $k === 0, "self generated"); + $k++; + } + + // load children + foreach ($this->getRandomPersons(0, 3) as $person) { + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $position = $this->getReference(LoadHouseholdPosition::CHILD); + + $movement->addMovement($date, $person, $position, $k === 0, "self generated"); + $k++; + } + + foreach ($movement->getPersistable() as $obj) { + print($obj->getStartDate()->format('Y-m-d')); + $manager->persist($obj); + } + } + + $manager->flush(); + } + + private function preparePersonIds() + { + $this->personIds = $this->em + ->createQuery('SELECT p.id FROM '.Person::class.' p '. + 'JOIN p.center c '. + 'WHERE c.name = :center ' + ) + ->setParameter('center', 'Center A') + ->getScalarResult() + ; + \shuffle($this->personIds); + } + + private function getRandomPersons(int $min, int $max) + { + $nb = \random_int($min, $max); + + for ($i=0; $i < $nb; $i++) { + $personId = \array_pop($this->personIds)['id']; + $persons[] = $this->em->getRepository(Person::class) + ->find($personId) + ; + } + + return $persons ?? []; + } + + public function getDependencies() + { + return [ + LoadPeople::class, + LoadHouseholdPosition::class + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php new file mode 100644 index 000000000..cf0bf3de1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHouseholdPosition.php @@ -0,0 +1,38 @@ +setLabel([ "fr" => $name ]) + ->setAllowHolder($allowHolder) + ->setShareHousehold($share) + ->setOrdering($ordering) + ; + + $manager->persist($position); + $this->addReference($ref, $position); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 27721012d..a28887c68 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -74,6 +74,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/form.yaml'); $loader->load('services/templating.yaml'); $loader->load('services/alt_names.yaml'); + $loader->load('services/household.yaml'); // We can get rid of this file when the service 'chill.person.repository.person' is no more used. // We should use the PersonRepository service instead of a custom service name. $loader->load('services/repository.yaml'); diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php similarity index 60% rename from src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php rename to src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php index d39034e32..89a9f3299 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php @@ -14,7 +14,7 @@ use Chill\PersonBundle\Entity\Household\Position; * name="chill_person_household_members" * ) */ -class HouseholdMembers +class HouseholdMember { /** * @ORM\Id @@ -26,27 +26,32 @@ class HouseholdMembers /** * @ORM\ManyToOne(targetEntity=Position::class) */ - private $position; + private ?Position $position = null; /** - * @ORM\Column(type="date") + * @ORM\Column(type="date", nullable=true, options={"default": null}) */ - private $startDate; + private ?\DateTimeImmutable $startDate = null; /** - * @ORM\Column(type="date") + * @ORM\Column(type="date", nullable= true, options={"default": null}) */ - private $endDate; + private ?\DateTimeImmutable $endDate = null; /** * @ORM\Column(type="string", length=255, nullable=true) */ - private $comment; + private ?string $comment = NULL; /** * @ORM\Column(type="boolean") */ - private $sharedHousehold; + private bool $sharedHousehold = false; + + /** + * @ORM\Column(type="boolean", options={"default": false}) + */ + private bool $holder = false; /** * @@ -55,7 +60,7 @@ class HouseholdMembers * targetEntity="\Chill\PersonBundle\Entity\Person" * ) */ - private $person; + private ?Person $person = null; /** * @@ -64,21 +69,28 @@ class HouseholdMembers * targetEntity="\Chill\PersonBundle\Entity\Household\Household" * ) */ - private $household; + private ?Household $household = null; + public function getId(): ?int { return $this->id; } - public function getPosition(): ?string + public function getPosition(): ?Position { return $this->position; } - public function setPosition(?string $position): self + public function setPosition(Position $position): self { + if ($this->position instanceof Position) { + throw new \LogicException("The position is already set. You cannot change ". + "a position of a membership"); + } + $this->position = $position; + $this->sharedHousehold = $position->getShareHousehold(); return $this; } @@ -119,17 +131,11 @@ class HouseholdMembers return $this; } - public function getSharedHousehold(): ?bool + public function getShareHousehold(): ?bool { return $this->sharedHousehold; } - public function setSharedHousehold(bool $sharedHousehold): self - { - $this->sharedHousehold = $sharedHousehold; - - return $this; - } public function getPerson(): ?Person { @@ -138,8 +144,15 @@ class HouseholdMembers public function setPerson(?Person $person): self { + if ($this->person instanceof Person) { + throw new \LogicException("You cannot change person ". + "on a membership"); + } + $this->person = $person; + $person->addHouseholdParticipation($this); + return $this; } @@ -150,8 +163,25 @@ class HouseholdMembers public function setHousehold(?Household $household): self { + if ($this->household instanceof Household) { + throw new \LogicException("You cannot change household ". + "on a membership"); + } + $this->household = $household; return $this; } + + public function setHolder(bool $holder): self + { + $this->holder = $holder; + + return $this; + } + + public function isHolder(): bool + { + return $this->holder; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php index 60e19b25a..070399661 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -55,12 +55,12 @@ class Position return $this; } - public function getShareHouseHold(): ?bool + public function getShareHousehold(): ?bool { return $this->shareHouseHold; } - public function setShareHouseHold(bool $shareHouseHold): self + public function setShareHousehold(bool $shareHouseHold): self { $this->shareHouseHold = $shareHouseHold; diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index a4fb185fb..0d9bb7cf6 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -26,6 +26,7 @@ use ArrayIterator; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Country; use Chill\PersonBundle\Entity\MaritalStatus; +use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\Address; use DateTime; @@ -272,6 +273,14 @@ class Person implements HasCenterInterface */ private $fullnameCanonical; + /** + * @ORM\OneToMany( + * targetEntity=HouseholdMember::class, + * mappedBy="person" + * ) + */ + private Collection $householdParticipations; + /** * Person constructor. * @@ -284,6 +293,7 @@ class Person implements HasCenterInterface $this->addresses = new ArrayCollection(); $this->altNames = new ArrayCollection(); $this->otherPhoneNumbers = new ArrayCollection(); + $this->householdParticipations = new ArrayCollection(); if ($opening === null) { $opening = new \DateTime(); @@ -1180,4 +1190,16 @@ class Person implements HasCenterInterface $this->fullnameCanonical = $fullnameCanonical; return $this; } + + public function addHouseholdParticipation(HouseholdMember $member): self + { + $this->householdParticipations[] = $member; + + return $this; + } + + public function getHouseholdParticipations(): Collection + { + return $this->householdParticipations; + } } diff --git a/src/Bundle/ChillPersonBundle/Household b/src/Bundle/ChillPersonBundle/Household deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembers.php b/src/Bundle/ChillPersonBundle/Household/MoveMembers.php new file mode 100644 index 000000000..495c4122b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Household/MoveMembers.php @@ -0,0 +1,80 @@ +validation = $validator; + } + + public function toHousehold(Household $household): self + { + $this->household = $household; + + return $this; + } + + public function addMovement(\DateTimeInterface $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self + { + if (NULL === $this->household) { + throw new \LogicException("You must define a household first"); + } + + $membership = (new HouseholdMember()) + ->setStartDate($date) + ->setPerson($person) + ->setPosition($position) + ->setHolder($holder) + ->setHousehold($this->household) + ->setComment($comment) + ; + + if ($position->getShareHousehold()) { + foreach ($person->getHouseholdParticipations() as $participation) { + if (FALSE === $participation->getShareHousehold()) { + continue; + } + + if ($participation === $membership) { + continue; + } + + if ($participation->getEndDate() === NULL || $participation->getEndDate() > $date) { + $participation->setEndDate($date); + $this->membershipsAffected[] = $participation; + } + } + } + + $this->membershipsAffected[] = $membership; + $this->persistables[] = $membership; + + return $this; + } + + public function validate(): ConstraintViolationListInterface + { + + } + + public function getPersistable(): array + { + return $this->persistables; + } +} diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php b/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php new file mode 100644 index 000000000..6a3910fda --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php @@ -0,0 +1,27 @@ +validator = $validator; + } + + public function createMovement(?Household $household): MoveMembers + { + $movement = new MoveMembers($this->validator); + + if ($household) { + $movement->toHousehold($household); + } + + return $movement; + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml b/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml index a6becd555..72bf899f4 100644 --- a/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml @@ -1,5 +1,6 @@ services: Chill\PersonBundle\DataFixtures\ORM\: + autowire: true resource: ../../DataFixtures/ORM tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillPersonBundle/config/services/household.yaml b/src/Bundle/ChillPersonBundle/config/services/household.yaml new file mode 100644 index 000000000..99c472d91 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/household.yaml @@ -0,0 +1,3 @@ +services: + Chill\PersonBundle\Household\MoveMembersFactory: + autowire: true diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php new file mode 100644 index 000000000..f9e94532a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528132405.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE chill_person_household_members ALTER startdate DROP NOT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members ALTER enddate DROP NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_household_members ALTER startDate SET NOT NULL'); + $this->addSql('ALTER TABLE chill_person_household_members ALTER endDate SET NOT NULL'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php new file mode 100644 index 000000000..4eaf149af --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528142121.php @@ -0,0 +1,29 @@ +addSql('ALTER TABLE chill_person_household_members ADD holder BOOLEAN DEFAULT FALSE NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_household_members DROP COLUMN holder'); + } +} From edc86af659631a5b17619953161cae7f13d31e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 18:01:20 +0200 Subject: [PATCH 4/8] add tests for moving houshold --- .../Tests/Household/MoveMembersTest.php | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php new file mode 100644 index 000000000..91a1d550d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php @@ -0,0 +1,103 @@ +createMock(ValidatorInterface::class); + + $this->factory = new MoveMembersFactory($validator); + } + + public function testMovePersonWithSharedHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(true) + ; + $household1 = new Household(); + $household2 = new Household(); + $editor = $this->factory->createMovement($household1); + + $editor->addMovement( + \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), + $person, + $position); + + $this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations()); + $this->assertEquals(1, $person->getHouseholdParticipations()->count()); + + $membership1 = $person->getHouseholdParticipations()->first(); + $this->assertSame($household1, $membership1->getHousehold()); + $this->assertNull($membership1->getEndDate()); + + // move to another household + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); + $editor = $this->factory->createMovement($household2); + $editor->addMovement( + $date, + $person, + $position); + + $this->assertEquals(2, $person->getHouseholdParticipations()->count()); + + $membership2 = $person->getHouseholdParticipations()->last(); + $this->assertSame($household2, $membership2->getHousehold()); + $this->assertNull($membership2->getEndDate()); + $this->assertNotNull($membership1->getEndDate(), + "assert that the membership1 is closed"); + $this->assertEquals($date, $membership1->getEndDate()); + } + + public function testMovePersonWithoutSharedHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(false) + ; + $household1 = new Household(); + $household2 = new Household(); + $editor = $this->factory->createMovement($household1); + + $editor->addMovement( + \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), + $person, + $position); + + $this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations()); + $this->assertEquals(1, $person->getHouseholdParticipations()->count()); + + $membership1 = $person->getHouseholdParticipations()->first(); + $this->assertSame($household1, $membership1->getHousehold()); + $this->assertNull($membership1->getEndDate()); + + // move to another household + $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); + $editor = $this->factory->createMovement($household2); + $editor->addMovement( + $date, + $person, + $position); + + $this->assertEquals(2, $person->getHouseholdParticipations()->count()); + + $membership2 = $person->getHouseholdParticipations()->last(); + $this->assertNull($membership2->getEndDate()); + $this->assertSame($household2, $membership2->getHousehold()); + $this->assertNull($membership1->getEndDate(), + "assert that the membership1 is not closed"); + } +} From ace3b1969e5a94bc6008e5ab9ee91e877fe84485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 May 2021 18:26:22 +0200 Subject: [PATCH 5/8] Rename membership edition and add more fixtures --- .../DataFixtures/ORM/LoadHousehold.php | 49 ++++++++++++++----- .../{MoveMembers.php => MembersEditor.php} | 12 ++--- .../Household/MembersEditorFactory.php | 21 ++++++++ .../Household/MoveMembersFactory.php | 27 ---------- ...eMembersTest.php => MembersEditorTest.php} | 16 +++--- .../config/services/household.yaml | 2 +- 6 files changed, 70 insertions(+), 57 deletions(-) rename src/Bundle/ChillPersonBundle/Household/{MoveMembers.php => MembersEditor.php} (92%) create mode 100644 src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php delete mode 100644 src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php rename src/Bundle/ChillPersonBundle/Tests/Household/{MoveMembersTest.php => MembersEditorTest.php} (88%) diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php index a8c3c73be..8dfca71f6 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadHousehold.php @@ -4,7 +4,7 @@ namespace Chill\PersonBundle\DataFixtures\ORM; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; -use Chill\PersonBundle\Household\MoveMembersFactory; +use Chill\PersonBundle\Household\MembersEditorFactory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; @@ -12,33 +12,53 @@ use Doctrine\Common\DataFixtures\DependentFixtureInterface; class LoadHousehold extends Fixture implements DependentFixtureInterface { - private MoveMembersFactory $movementFactory; + private MembersEditorFactory $editorFactory; private EntityManagerInterface $em; private CONST NUMBER_OF_HOUSEHOLD = 10; - public function __construct(MoveMembersFactory $movementFactory, EntityManagerInterface $em) + public function __construct(MembersEditorFactory $editorFactory, EntityManagerInterface $em) { - $this->movementFactory = $movementFactory; + $this->editorFactory = $editorFactory; $this->em = $em; } public function load(ObjectManager $manager) { + // generate two times the participation. This will lead to + // some movement in participation (same people in two differents + // households) + $this->preparePersonIds(); + $this->generateHousehold( + $manager, + \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') + ); + + $this->preparePersonIds(); + + $this->generateHousehold( + $manager, + \DateTimeImmutable::createFromFormat('Y-m-d', '2015-01-01') + ); + + $manager->flush(); + } + + private function generateHousehold(ObjectManager $manager, \DateTimeImmutable $startDate) + { for ($i=0; $i < self::NUMBER_OF_HOUSEHOLD; $i++) { $household = new Household(); $manager->persist($household); - $movement = $this->movementFactory->createMovement($household); + $movement = $this->editorFactory->createEditor($household); // load adults $k = 0; foreach ($this->getRandomPersons(1, 3) as $person) { - $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') - ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); $position = $this->getReference(LoadHouseholdPosition::ADULT); $movement->addMovement($date, $person, $position, $k === 0, "self generated"); @@ -47,21 +67,26 @@ class LoadHousehold extends Fixture implements DependentFixtureInterface // load children foreach ($this->getRandomPersons(0, 3) as $person) { - $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01') - ->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); $position = $this->getReference(LoadHouseholdPosition::CHILD); $movement->addMovement($date, $person, $position, $k === 0, "self generated"); $k++; } + // load children out + foreach ($this->getRandomPersons(0, 2) as $person) { + $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W')); + $position = $this->getReference(LoadHouseholdPosition::CHILD_OUT); + + $movement->addMovement($date, $person, $position, $k === 0, "self generated"); + $k++; + } + foreach ($movement->getPersistable() as $obj) { - print($obj->getStartDate()->format('Y-m-d')); $manager->persist($obj); } } - - $manager->flush(); } private function preparePersonIds() diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembers.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php similarity index 92% rename from src/Bundle/ChillPersonBundle/Household/MoveMembers.php rename to src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 495c4122b..8dfd11b7b 100644 --- a/src/Bundle/ChillPersonBundle/Household/MoveMembers.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -10,7 +10,7 @@ use Chill\PersonBundle\Entity\Person; use Symfony\Component\Validator\Validator\ValidatorInterface; -class MoveMembers +class MembersEditor { private ValidatorInterface $validator; private Household $household; @@ -18,18 +18,12 @@ class MoveMembers private array $persistables = []; private array $memershipsAffected = []; - public function __construct(ValidatorInterface $validator) + public function __construct(ValidatorInterface $validator, Household $household) { $this->validation = $validator; + $this->household = $household; } - public function toHousehold(Household $household): self - { - $this->household = $household; - - return $this; - } - public function addMovement(\DateTimeInterface $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self { if (NULL === $this->household) { diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php new file mode 100644 index 000000000..611308b19 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php @@ -0,0 +1,21 @@ +validator = $validator; + } + + public function createEditor(Household $household): MembersEditor + { + return new MembersEditor($this->validator, $household); + } +} diff --git a/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php b/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php deleted file mode 100644 index 6a3910fda..000000000 --- a/src/Bundle/ChillPersonBundle/Household/MoveMembersFactory.php +++ /dev/null @@ -1,27 +0,0 @@ -validator = $validator; - } - - public function createMovement(?Household $household): MoveMembers - { - $movement = new MoveMembers($this->validator); - - if ($household) { - $movement->toHousehold($household); - } - - return $movement; - } -} diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php similarity index 88% rename from src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php rename to src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index 91a1d550d..ee3e2df86 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MoveMembersTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -5,21 +5,21 @@ namespace Chill\PersonBundle\Tests\Household; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Position; -use Chill\PersonBundle\Household\MoveMembersFactory; +use Chill\PersonBundle\Household\MembersEditorFactory; use Doctrine\Common\Collections\Collection; use Symfony\Component\Validator\Validator\ValidatorInterface; use PHPUnit\Framework\TestCase; -class MoveMembersTest extends TestCase +class MembersEditorTest extends TestCase { - private MoveMembersFactory $factory; + private MembersEditorFactory $factory; protected function setUp() { $validator = $this->createMock(ValidatorInterface::class); - $this->factory = new MoveMembersFactory($validator); + $this->factory = new MembersEditorFactory($validator); } public function testMovePersonWithSharedHousehold() @@ -30,7 +30,7 @@ class MoveMembersTest extends TestCase ; $household1 = new Household(); $household2 = new Household(); - $editor = $this->factory->createMovement($household1); + $editor = $this->factory->createEditor($household1); $editor->addMovement( \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), @@ -46,7 +46,7 @@ class MoveMembersTest extends TestCase // move to another household $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); - $editor = $this->factory->createMovement($household2); + $editor = $this->factory->createEditor($household2); $editor->addMovement( $date, $person, @@ -70,7 +70,7 @@ class MoveMembersTest extends TestCase ; $household1 = new Household(); $household2 = new Household(); - $editor = $this->factory->createMovement($household1); + $editor = $this->factory->createEditor($household1); $editor->addMovement( \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'), @@ -86,7 +86,7 @@ class MoveMembersTest extends TestCase // move to another household $date = \DateTimeImmutable::createFromFormat('Y-m-d', '2021-01-01'); - $editor = $this->factory->createMovement($household2); + $editor = $this->factory->createEditor($household2); $editor->addMovement( $date, $person, diff --git a/src/Bundle/ChillPersonBundle/config/services/household.yaml b/src/Bundle/ChillPersonBundle/config/services/household.yaml index 99c472d91..300928c49 100644 --- a/src/Bundle/ChillPersonBundle/config/services/household.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/household.yaml @@ -1,3 +1,3 @@ services: - Chill\PersonBundle\Household\MoveMembersFactory: + Chill\PersonBundle\Household\MembersEditorFactory: autowire: true From 041b1dfc51fe1dcb74553578d0b3ef8f4b969725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 31 May 2021 20:42:07 +0200 Subject: [PATCH 6/8] Controller action to move members of an household --- .../Controller/HouseholdMemberController.php | 50 ++++++++++ .../Entity/Household/Household.php | 5 + .../Entity/Household/HouseholdMember.php | 15 ++- .../Entity/Household/Position.php | 5 + .../Household/MembersEditor.php | 9 +- .../Household/PositionRepository.php | 30 +----- .../Normalizer/MembersEditorNormalizer.php | 75 ++++++++++++++ .../HouseholdMemberControllerTest.php | 98 +++++++++++++++++++ .../Entity/Household/HouseholdMemberTest.php | 34 +++++++ .../Tests/Household/MembersEditorTest.php | 18 ++-- .../ChillPersonBundle/chill.api.specs.yaml | 62 ++++++++++++ .../migrations/Version20210528092625.php | 3 +- 12 files changed, 356 insertions(+), 48 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php create mode 100644 src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdMemberTest.php diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php new file mode 100644 index 000000000..c6fd4dd59 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php @@ -0,0 +1,50 @@ +getSerializer() + ->deserialize($request->getContent(), MembersEditor::class, + $_format, ['groups' => [ "read" ]]); + } catch (Exception\InvalidArgumentException | Exception\UnexpectedValueException $e) { + throw new BadRequestException("Deserialization error: {$e->getMessage()}", 45896, $e); + } + dump($editor); + // TODO ACL + // + // TODO validation + // + $em = $this->getDoctrine()->getManager(); + + // to ensure closing membership before creating one, we must manually open a transaction + $em->beginTransaction(); + + foreach ($editor->getPersistable() as $el) { + $em->persist($el); + } + + $em->flush(); + $em->commit(); + + + return $this->json($editor->getHousehold(), Response::HTTP_OK, ["groups" => ["read"]]); + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index 420325d6d..ac22b6e8b 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -5,12 +5,16 @@ namespace Chill\PersonBundle\Entity\Household; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\Collection; use Chill\MainBundle\Entity\Address; +use Symfony\Component\Serializer\Annotation as Serializer; /** * @ORM\Entity * @ORM\Table( * name="chill_person_household" * ) + * @Serializer\DiscriminatorMap(typeProperty="type", mapping={ + * "household"=Household::class + * }) */ class Household { @@ -18,6 +22,7 @@ class Household * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups({"read"}) */ private $id; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php index 89a9f3299..52bd969fc 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php @@ -29,12 +29,12 @@ class HouseholdMember private ?Position $position = null; /** - * @ORM\Column(type="date", nullable=true, options={"default": null}) + * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) */ private ?\DateTimeImmutable $startDate = null; /** - * @ORM\Column(type="date", nullable= true, options={"default": null}) + * @ORM\Column(type="date_immutable", nullable= true, options={"default": null}) */ private ?\DateTimeImmutable $endDate = null; @@ -95,24 +95,24 @@ class HouseholdMember return $this; } - public function getStartDate(): ?\DateTimeInterface + public function getStartDate(): ?\DateTimeImmutable { return $this->startDate; } - public function setStartDate(\DateTimeInterface $startDate): self + public function setStartDate(\DateTimeImmutable $startDate): self { $this->startDate = $startDate; return $this; } - public function getEndDate(): ?\DateTimeInterface + public function getEndDate(): ?\DateTimeImmutable { return $this->endDate; } - public function setEndDate(\DateTimeInterface $endDate): self + public function setEndDate(\DateTimeImmutable $endDate): self { $this->endDate = $endDate; @@ -150,8 +150,7 @@ class HouseholdMember } $this->person = $person; - - $person->addHouseholdParticipation($this); + $this->person->addHouseholdParticipation($this); return $this; } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php index 070399661..156631b63 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -4,10 +4,14 @@ namespace Chill\PersonBundle\Entity\Household; use Chill\PersonBundle\Repository\Household\PositionRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** * @ORM\Entity(repositoryClass=PositionRepository::class) * @ORM\Table(name="chill_person_household_position") + * @Serializer\DiscriminatorMap(typeProperty="type", mapping={ + * "household_position"=Position::class + * }) */ class Position { @@ -15,6 +19,7 @@ class Position * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups({ "read" }) */ private $id; diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 8dfd11b7b..527404c75 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -16,7 +16,7 @@ class MembersEditor private Household $household; private array $persistables = []; - private array $memershipsAffected = []; + private array $membershipsAffected = []; public function __construct(ValidatorInterface $validator, Household $household) { @@ -24,7 +24,7 @@ class MembersEditor $this->household = $household; } - public function addMovement(\DateTimeInterface $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self + public function addMovement(\DateTimeImmutable $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self { if (NULL === $this->household) { throw new \LogicException("You must define a household first"); @@ -71,4 +71,9 @@ class MembersEditor { return $this->persistables; } + + public function getHousehold(): Household + { + return $this->household; + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php index 0ca9ef10e..6d07f791e 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Household/PositionRepository.php @@ -2,7 +2,7 @@ namespace Chill\PersonBundle\Repository\Household; -use App\Entity\Chill\PersonBundle\Entity\Household\Position; +use Chill\PersonBundle\Entity\Household\Position; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -19,32 +19,4 @@ class PositionRepository extends ServiceEntityRepository parent::__construct($registry, Position::class); } - // /** - // * @return Position[] Returns an array of Position objects - // */ - /* - public function findByExampleField($value) - { - return $this->createQueryBuilder('p') - ->andWhere('p.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('p.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; - } - */ - - /* - public function findOneBySomeField($value): ?Position - { - return $this->createQueryBuilder('p') - ->andWhere('p.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; - } - */ } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php new file mode 100644 index 000000000..603c1a9e5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/MembersEditorNormalizer.php @@ -0,0 +1,75 @@ +factory = $factory; + } + + public function denormalize($data, string $type, string $format = null, array $context = []) + { + $household = $this->denormalizer->denormalize($data['destination'], Household::class, + $format, $context); + + if (NULL === $household) { + throw new Exception\InvalidArgumentException("household could not be denormalized. Impossible to process"); + } + + $editor = $this->factory->createEditor($household); + + if (NULL == $data['concerned'] ?? [] + && FALSE === ·\is_array('concerned')) { + throw new Exception\UnexpectedValueException("The schema does not have any key 'concerned'"); + } + + foreach ($data['concerned'] as $key => $concerned) { + $person = $this->denormalizer->denormalize($concerned['person'] ?? null, Person::class, + $format, $context); + $position = $this->denormalizer->denormalize($concerned['position'] ?? null, Position::class, + $format, $context); + $startDate = $this->denormalizer->denormalize($concerned['start_date'] ?? null, \DateTimeImmutable::class, + $format, $context); + + $holder = (bool) $concerned['holder'] ?? false; + $comment = (string) $concerned['comment'] ?? false; + + if ( + NULL === $person + && NULL === $position + && NULL === $startDate + ) { + throw new Exception\InvalidArgumentException("position with ". + "key $key could not be denormalized: missing ". + "person, position or start_date."); + } + + $editor->addMovement($startDate, $person, $position, $holder, + $comment); + + return $editor; + } + } + + public function supportsDenormalization($data, string $type, string $format = null) + { + return $type === MembersEditor::class; + } + +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php new file mode 100644 index 000000000..96cb81382 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php @@ -0,0 +1,98 @@ +getClientAuthenticated(); + + $client->request( + Request::METHOD_POST, + '/api/1.0/person/household/members/move.json', + [], // parameters + [], // files + [], // server + \json_encode( + [ + 'concerned' => + [ + [ + 'person' => + [ + 'type' => 'person', + 'id' => $personId + ], + 'start_date' => + [ + 'datetime' => $date->format(\DateTimeInterface::RFC3339) + ], + 'position' => + [ + 'type' => 'household_position', + 'id' => $positionId + ], + 'holder' => false, + 'comment' => "Introduced by automated test", + ], + ], + 'destination' => + [ + 'type' => 'household', + 'id' => $householdId + ] + ], + true) + ); + + $this->assertEquals(Response::HTTP_OK, + $client->getResponse()->getStatusCode() + ); + } + + public function provideValidData(): \Iterator + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $personIds = $em->createQuery("SELECT p.id FROM ".Person::class." p ". + "JOIN p.center c WHERE c.name = :center") + ->setParameter('center', "Center A") + ->setMaxResults(100) + ->getScalarResult() + ; + \shuffle($personIds); + + $household = new Household(); + $em->persist($household); + $em->flush(); + + $positions = $em->createQuery("SELECT pos.id FROM ".Position::class." pos ". + "WHERE pos.shareHouseHold = TRUE") + ->getResult() + ; + + yield [ + \array_pop($personIds)['id'], + $household->getId(), + $positions[\random_int(0, count($positions) - 1)]['id'], + new \DateTimeImmutable('today') + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdMemberTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdMemberTest.php new file mode 100644 index 000000000..f18a62781 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdMemberTest.php @@ -0,0 +1,34 @@ +setShareHousehold(true) + ; + $membership = (new HouseholdMember()) + ->setPosition($position) + ; + + $this->assertTrue($membership->getShareHousehold()); + } + + public function testPositionDoNotSharehousehold() + { + $position = (new Position()) + ->setShareHousehold(false) + ; + $membership = (new HouseholdMember()) + ->setPosition($position) + ; + + $this->assertFalse($membership->getShareHousehold()); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index ee3e2df86..8ef409296 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -37,10 +37,10 @@ class MembersEditorTest extends TestCase $person, $position); - $this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations()); - $this->assertEquals(1, $person->getHouseholdParticipations()->count()); + $persistables = $editor->getPersistable(); + $this->assertEquals(\count($persistables), 1); - $membership1 = $person->getHouseholdParticipations()->first(); + $membership1 = $persistables[0]; $this->assertSame($household1, $membership1->getHousehold()); $this->assertNull($membership1->getEndDate()); @@ -52,9 +52,10 @@ class MembersEditorTest extends TestCase $person, $position); - $this->assertEquals(2, $person->getHouseholdParticipations()->count()); + $persistables = $editor->getPersistable(); + $this->assertEquals(1, count($persistables)); - $membership2 = $person->getHouseholdParticipations()->last(); + $membership2 = $persistables[0]; $this->assertSame($household2, $membership2->getHousehold()); $this->assertNull($membership2->getEndDate()); $this->assertNotNull($membership1->getEndDate(), @@ -77,8 +78,8 @@ class MembersEditorTest extends TestCase $person, $position); - $this->assertInstanceOf(Collection::class, $person->getHouseholdParticipations()); - $this->assertEquals(1, $person->getHouseholdParticipations()->count()); + $persistables = $editor->getPersistable(); + $this->assertEquals(1, count($persistables)); $membership1 = $person->getHouseholdParticipations()->first(); $this->assertSame($household1, $membership1->getHousehold()); @@ -92,7 +93,8 @@ class MembersEditorTest extends TestCase $person, $position); - $this->assertEquals(2, $person->getHouseholdParticipations()->count()); + $persistables = $editor->getPersistable(); + $this->assertEquals(1, count($persistables)); $membership2 = $person->getHouseholdParticipations()->last(); $this->assertNull($membership2->getEndDate()); diff --git a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml index c3067be55..0fac8e6ce 100644 --- a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml @@ -192,6 +192,25 @@ components: text: type: string readOnly: true + Household: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'household' + HouseholdPosition: + type: object + properties: + id: + type: integer + type: + type: string + enum: + - 'household_position' + paths: /1.0/person/person/{id}.json: @@ -764,3 +783,46 @@ paths: description: "OK" 400: description: "transition cannot be applyed" + + /1.0/person/household/members/move.json: + post: + tags: + - household + summary: move one or multiple person from a household to another + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + concerned: + type: array + items: + type: object + properties: + person: + $ref: '#/components/schemas/PersonById' + start_date: + $ref: '#/components/schemas/Date' + position: + $ref: '#/components/schemas/HouseholdPosition' + holder: + type: boolean + comment: + type: string + destination: + oneOf: + - $ref: '#/components/schemas/Household' + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "Unprocessable entity (validation errors)" + 400: + description: "transition cannot be applyed" + diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php b/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php index 5d1909424..1ce855140 100644 --- a/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210528092625.php @@ -42,7 +42,8 @@ final class Version20210528092625 extends AbstractMigration -- extension btree_gist required to include comparaison with integer person_id WITH =, daterange(startdate, enddate) WITH && - ) WHERE (sharedhousehold IS TRUE)"); + ) WHERE (sharedhousehold IS TRUE) + INITIALLY DEFERRED"); // rename constraints $this->addSql('ALTER TABLE public.chill_person_household_to_addresses DROP CONSTRAINT fk_7109483e79ff843'); From 17481798fb3c5f931d5193fc96561b3d85cc683e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 2 Jun 2021 16:38:59 +0200 Subject: [PATCH 7/8] fix duplicate in messages.fr.yml --- src/Bundle/ChillPersonBundle/translations/messages.fr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 1912cdaf2..e9befc441 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -179,7 +179,6 @@ See accompanying periods: Voir les périodes d'accompagnement See accompanying period: Voir cette période d'accompagnement Referrer: Référent -Participants: Personnes impliquées # pickAPersonType Pick a person: Choisir une personne From 55fa16ce5652c9c3c0d8cd85211361704683a6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 4 Jun 2021 14:54:14 +0200 Subject: [PATCH 8/8] fix missing postgresql extension: correct class name --- .gitlab-ci.yml | 2 +- src/Bundle/ChillMainBundle/migrations/Version20210528090000.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6973ff6d7..86e65c390 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ cache: before_script: # add extensions to postgres - - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gist;" + - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm;" # Install and run Composer - curl -sS https://getcomposer.org/installer | php - php -d memory_limit=2G composer.phar install diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php index de3c7336a..d2a33230f 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20210528090000.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Add extension btree_gist */ -final class Version20210528104651 extends AbstractMigration +final class Version20210528090000 extends AbstractMigration { public function getDescription(): string {