From 3d008b6f71b67f8072d147cce94eae53dd00eb69 Mon Sep 17 00:00:00 2001 From: Boris Waaub Date: Fri, 13 Mar 2026 14:40:43 +0000 Subject: [PATCH] Resolve "Import des usagers depuis une source externe" --- composer.json | 6 +- package.json | 1 + .../Upsert/Handler/PersonUpsertHandler.php | 290 ++++++ .../Actions/Upsert/UpsertMessage.php | 40 + .../Repository/PersonRepository.php | 5 + .../Handler/PersonUpsertHandlerTest.php | 949 ++++++++++++++++++ .../src/Resources/public/types.ts | 5 +- 7 files changed, 1290 insertions(+), 6 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Actions/Upsert/Handler/PersonUpsertHandler.php create mode 100644 src/Bundle/ChillPersonBundle/Actions/Upsert/UpsertMessage.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Action/Upsert/Handler/PersonUpsertHandlerTest.php diff --git a/composer.json b/composer.json index d9c7a51db..175fbc512 100644 --- a/composer.json +++ b/composer.json @@ -98,7 +98,7 @@ "require-dev": { "doctrine/doctrine-fixtures-bundle": "^3.3", "fakerphp/faker": "^1.13", - "friendsofphp/php-cs-fixer": "3.93.0", + "friendsofphp/php-cs-fixer": "^3.94", "jangregor/phpstan-prophecy": "^1.0", "nelmio/alice": "^3.8", "nikic/php-parser": "^4.15", @@ -113,13 +113,13 @@ "symfony/debug-bundle": "^5.4", "symfony/dotenv": "^5.4", "symfony/flex": "^2.4", + "symfony/loco-translation-provider": "^6.0", "symfony/maker-bundle": "^1.20", "symfony/phpunit-bridge": "^7.1", "symfony/runtime": "^5.4", "symfony/stopwatch": "^5.4", "symfony/var-dumper": "^5.4", - "symfony/web-profiler-bundle": "^5.4", - "symfony/loco-translation-provider": "^6.0" + "symfony/web-profiler-bundle": "^5.4" }, "conflict": { "symfony/symfony": "*" diff --git a/package.json b/package.json index 12a5fee26..cd4f23a07 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@hotwired/stimulus": "^3.0.0", "@luminateone/eslint-baseline": "^1.0.9", "@symfony/stimulus-bridge": "^3.2.0", + "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@types/dompurify": "^3.0.5", diff --git a/src/Bundle/ChillPersonBundle/Actions/Upsert/Handler/PersonUpsertHandler.php b/src/Bundle/ChillPersonBundle/Actions/Upsert/Handler/PersonUpsertHandler.php new file mode 100644 index 000000000..6629cd63b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Actions/Upsert/Handler/PersonUpsertHandler.php @@ -0,0 +1,290 @@ +addressStreet) { + $newAddress->setStreet($message->addressStreet); + } + if (null !== $message->addressStreetNumber) { + $newAddress->setStreetNumber($message->addressStreetNumber); + } + if (null !== $message->addressPostcode) { + $postalCode = $this->postalCodeRepository->findOneBy(['code' => $message->addressPostcode]); + if (null !== $postalCode) { + $newAddress->setPostcode($postalCode); + } + } + if (null !== $message->addressExtra) { + $newAddress->setExtra($message->addressExtra); + } + $newAddress->setValidFrom(\DateTime::createFromImmutable($this->clock->now())); + + return $newAddress; + } + + private function isMessageAddressMatch(Address $existingAddress, UpsertMessage $message): bool + { + $streetMatches = $message->addressStreet === $existingAddress->getStreet(); + $streetNumberMatches = $message->addressStreetNumber === $existingAddress->getStreetNumber(); + $postcodeMatches = null !== $existingAddress->getPostcode() + && $message->addressPostcode === $existingAddress->getPostcode()->getCode(); + + return $streetMatches && $streetNumberMatches && $postcodeMatches; + } + + private function findGenderByValue(?string $genderValue): ?Gender + { + if (null === $genderValue) { + return null; + } + + foreach (GenderEnum::cases() as $case) { + if ($case->value === $genderValue) { + $genders = $this->genderRepository->findByGenderTranslation($case); + + return count($genders) > 0 ? $genders[0] : null; + } + } + + return null; + } + + private function findNeutralGender(): ?Gender + { + $genders = $this->genderRepository->findByGenderTranslation(GenderEnum::NEUTRAL); + + return count($genders) > 0 ? $genders[0] : null; + } + + private function findCenterByName(?string $centerName): ?Center + { + if (null === $centerName) { + return null; + } + + $centers = $this->centerRepository->findBy(['name' => $centerName]); + + return count($centers) > 0 ? $centers[0] : null; + } + + private function handlePersonMessage(UpsertMessage $message, Person $person): Person + { + $currentHousehold = $person->getCurrentHousehold(); + + // Check if address information is provided in the message + $hasAddressInfo = $message->hasAddressInfo(); + + if (null !== $currentHousehold && $hasAddressInfo) { + $lastCurrentAddress = $currentHousehold->getCurrentAddress(); + + if (null !== $lastCurrentAddress) { + $messageAddressMatch = $this->isMessageAddressMatch($lastCurrentAddress, $message); + if (!$messageAddressMatch) { + $newAddress = $this->createAddressWithMessage($message); + $currentHousehold->addAddress($newAddress); + } + } + } elseif (null === $currentHousehold && $hasAddressInfo) { + // Create a new household with a new address + $newHousehold = new Household(); + + // Create the new address + $newAddress = $this->createAddressWithMessage($message); + $newHousehold->addAddress($newAddress); + + // Create a MembersEditor with the new household and add the person + $membersEditor = $this->membersEditorFactory->createEditor($newHousehold); + $membersEditor->addMovement( + $this->clock->now(), + $person, + null, + true + ); + + // Validate the membership + $violations = $membersEditor->validate(); + if (0 === $violations->count()) { + // Persist the household and trigger events + $this->entityManager->persist($newHousehold); + foreach ($membersEditor->getPersistable() as $persistable) { + $this->entityManager->persist($persistable); + } + $membersEditor->postMove(); + } + } + + // Update person information + if (null !== $message->firstName) { + $person->setFirstName($message->firstName); + } + if (null !== $message->lastName) { + $person->setLastName($message->lastName); + } + + // Handle birthDate + if (null !== $message->birthdate) { + try { + $person->setBirthdate(new \DateTime($message->birthdate)); + } catch (\Exception $e) { + $this->logger->error(self::LOG_PREFIX.'Could not parse birthdate: '.$message->birthdate, [ + 'exception' => $e->getTraceAsString(), + 'birthdate' => $message->birthdate, + ]); + } + } + + // Handle gender + $gender = $this->findGenderByValue($message->gender); + if (null === $gender) { + // If no gender found or provided, use neutral as default + $gender = $this->findNeutralGender(); + } + if (null !== $gender) { + $person->setGender($gender); + } + + // Handle center + $center = $this->findCenterByName($message->center); + if (null !== $center) { + $person->setCenter($center); + } + + // mobileNumber and phoneNumber + if (null !== $message->phoneNumber) { + try { + $person->setPhonenumber($this->phoneNumberUtil->parse($message->phoneNumber)); + } catch (NumberParseException $e) { + $this->logger->error(self::LOG_PREFIX.'Could not parse phoneNumber', [ + 'exception' => $e->getTraceAsString(), + 'phoneNumber' => $message->phoneNumber, + ]); + throw new UnrecoverableMessageHandlingException('Could not parse phoneNumber: '.$message->phoneNumber); + } + } + + if (null !== $message->mobileNumber) { + try { + $person->setMobilenumber($this->phoneNumberUtil->parse($message->mobileNumber)); + } catch (NumberParseException $e) { + $this->logger->error(self::LOG_PREFIX.'Could not parse mobileNumber', [ + 'exception' => $e->getTraceAsString(), + 'mobileNumber' => $message->mobileNumber, + ]); + throw new UnrecoverableMessageHandlingException('Could not parse mobileNumber: '.$message->mobileNumber); + } + } + + $errors = $this->validator->validate($person); + + if ($errors->count() > 0) { + $errorMessages = []; + foreach ($errors as $error) { + $errorMessages[] = $error->getMessage(); + } + $this->logger->error(self::LOG_PREFIX.'Person created / updated not valid', ['errors' => implode(', ', $errorMessages)]); + throw new UnrecoverableMessageHandlingException('Person created / updated not valid: '.implode(', ', $errorMessages)); + } + + return $person; + } + + public function __invoke(UpsertMessage $message): void + { + // 1. Retrieve definition + $definition = $this->personIdentifierDefinitionRepository->find($message->personIdentifierDefinitionId); + if (null === $definition) { + $this->logger->error(self::LOG_PREFIX.'Person message not found: '.$message->personIdentifierDefinitionId); + throw new UnrecoverableMessageHandlingException('PersonIdentifierDefinition not found for id '.$message->personIdentifierDefinitionId); + } + + // 2. Search identifiers + $identifiers = $this->personIdentifierRepository->findByDefinitionAndCanonical($definition, $message->externalId); + + if (count($identifiers) > 1) { + $this->logger->error(self::LOG_PREFIX.'Person message contains more than one identifier'); + throw new UnrecoverableMessageHandlingException('More than one identifier found for definition.'); + } + + if (0 === count($identifiers)) { + // 3. Create new entity Person + $person = new Person(); + $person = $this->handlePersonMessage($message, $person); + $this->entityManager->persist($person); + + // Create and bound identifier + $identifier = new PersonIdentifier($definition); + $identifier->setPerson($person); + $identifier->setValue($value = ['content' => $message->externalId]); + $identifier->setCanonical( + $this->personIdentifierManager + ->buildWorkerByPersonIdentifierDefinition($identifier->getDefinition()) + ->canonicalizeValue($value) + ); + $this->entityManager->persist($identifier); + } else { + // 4. Update existing person + /** @var PersonIdentifier $identifier */ + $identifier = $identifiers[0]; + $person = $identifier->getPerson(); + $this->handlePersonMessage($message, $person); + } + + $this->entityManager->flush(); + $this->entityManager->clear(); + } +} diff --git a/src/Bundle/ChillPersonBundle/Actions/Upsert/UpsertMessage.php b/src/Bundle/ChillPersonBundle/Actions/Upsert/UpsertMessage.php new file mode 100644 index 000000000..99ac19920 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Actions/Upsert/UpsertMessage.php @@ -0,0 +1,40 @@ +addressStreet || null !== $this->addressPostcode || null !== $this->addressStreetNumber; + } +} diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php index 8d5e244ea..d0db5fbcc 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php @@ -73,6 +73,11 @@ class PersonRepository implements ObjectRepository return $this->repository->findBy(['id' => $ids]); } + public function findByExternalId(string $externalId): ?Person + { + return $this->repository->findOneBy(['externalId' => $externalId]); + } + /** * @throws \Exception * diff --git a/src/Bundle/ChillPersonBundle/Tests/Action/Upsert/Handler/PersonUpsertHandlerTest.php b/src/Bundle/ChillPersonBundle/Tests/Action/Upsert/Handler/PersonUpsertHandlerTest.php new file mode 100644 index 000000000..8c4a72a16 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Action/Upsert/Handler/PersonUpsertHandlerTest.php @@ -0,0 +1,949 @@ +prophesize(MembersEditor::class); + $violations = $this->prophesize(ConstraintViolationListInterface::class); + $violations->count()->willReturn(0); + $membersEditor->validate()->willReturn($violations->reveal()); + $membersEditor->addMovement(Argument::type(\DateTimeImmutable::class), Argument::type(Person::class), null, true)->will(function ($args) use ($membersEditor) { + $date = $args[0]; + $person = $args[1]; + $household = $membersEditor->reveal()->getHousehold(); + + if ($household instanceof Household) { + $membership = (new HouseholdMember()) + ->setStartDate($date) + ->setPerson($person); + + $household->addMember($membership); + } + + return $membersEditor->reveal(); + }); + $membersEditor->getPersistable()->willReturn([]); + $membersEditor->postMove(); + + $factory = $this->prophesize(MembersEditorFactory::class); + $factory->createEditor()->will(function ($args) use ($membersEditor) { + $membersEditor->getHousehold()->willReturn(new Household()); + + return $membersEditor->reveal(); + }); + $factory->createEditor(Argument::type(Household::class))->will(function ($args) use ($membersEditor) { + $membersEditor->getHousehold()->willReturn($args[0]); + + return $membersEditor->reveal(); + }); + + return $factory->reveal(); + } + + public function testInvokeCreatesNewPersonAndIdentifier(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $gender = new Gender(); + $gender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender]); + + // Mock center repository - no center found + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonIdentifier && '123' === $arg->getCanonical() && $arg->getValue() === ['content' => '123']))->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertEquals('John', $person->getFirstName()); + self::assertEquals('Doe', $person->getLastName()); + } + + public function testInvokeUpdatesExistingPerson(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(2)->willReturn($definition); + + $person = new Person(); + + $identifier = new PersonIdentifier($definition); + $identifier->setPerson($person); + + $identifierRepository->findByDefinitionAndCanonical($definition, '456')->willReturn([$identifier]); + + // Mock gender repository - ensure it always returns an array + $gender = new Gender(); + $gender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender]); + + // Mock center repository - no center found + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '456'; + $message->personIdentifierDefinitionId = 2; + $message->firstName = 'Jane'; + $message->lastName = 'Smith'; + + $handler->__invoke($message); + + self::assertEquals('Jane', $person->getFirstName()); + self::assertEquals('Smith', $person->getLastName()); + } + + public function testInvokeWithGenderHandling(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository - configure to return male gender + $maleGender = new Gender(); + $maleGender->setGenderTranslation(GenderEnum::MALE); + $genderRepository->findByGenderTranslation(GenderEnum::MALE)->willReturn([$maleGender]); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$maleGender]); + + // Mock center repository + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + $message->gender = 'male'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertSame($maleGender, $person->getGender()); + } + + public function testInvokeWithCenterHandling(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $neutralGender = new Gender(); + $neutralGender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(GenderEnum::NEUTRAL)->willReturn([$neutralGender]); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$neutralGender]); + + // Mock center repository - find center by name + $center = new \Chill\MainBundle\Entity\Center(); + $center->setName('Main Center'); + $centerRepository->findBy(['name' => 'Main Center'])->willReturn([$center]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + $message->center = 'Main Center'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertSame($center, $person->getCenter()); + } + + public function testInvokeWithGenderAndCenterHandling(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository - configure to return female gender + $femaleGender = new Gender(); + $femaleGender->setGenderTranslation(GenderEnum::FEMALE); + $genderRepository->findByGenderTranslation(GenderEnum::FEMALE)->willReturn([$femaleGender]); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$femaleGender]); + + // Mock center repository - find center by name + $center = new \Chill\MainBundle\Entity\Center(); + $center->setName('Secondary Center'); + $centerRepository->findBy(['name' => 'Secondary Center'])->willReturn([$center]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'Jane'; + $message->lastName = 'Smith'; + $message->gender = 'female'; + $message->center = 'Secondary Center'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertSame($femaleGender, $person->getGender()); + self::assertSame($center, $person->getCenter()); + } + + public function testInvokeWithBirthdate(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $gender = new Gender(); + $gender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender]); + + // Mock center repository + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + $message->birthdate = '1990-01-15'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertEquals(new \DateTime('1990-01-15'), $person->getBirthdate()); + } + + public function testInvokeWithPhoneNumber(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $gender = new Gender(); + $gender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender]); + + // Mock center repository + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock phone number parsing + $phoneNumber = $this->prophesize(\libphonenumber\PhoneNumber::class); + $phoneNumberUtil->parse('+32123456789')->willReturn($phoneNumber->reveal()); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + $message->phoneNumber = '+32123456789'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertSame($phoneNumber->reveal(), $person->getPhonenumber()); + } + + public function testInvokeWithMobileNumber(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock(); + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $gender = new Gender(); + $gender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender]); + + // Mock center repository + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList()); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil, + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + $message->mobileNumber = '+32475123456'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertEquals('475123456', (string) $person->getMobilenumber()->getNationalNumber()); + self::assertEquals('32', $person->getMobilenumber()->getCountryCode()); + } + + public function testInvokeWithAddressFields(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock('2026-03-09'); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $gender = new Gender(); + $gender->setGenderTranslation(GenderEnum::NEUTRAL); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender]); + + // Mock center repository + $centerRepository->findBy(['name' => null])->willReturn([]); + + // Mock postal code repository + $postalCode = new \Chill\MainBundle\Entity\PostalCode(); + $postalCode->setCode('1000'); + $postalCodeRepository->findOneBy(['code' => '1000'])->willReturn($postalCode); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'John'; + $message->lastName = 'Doe'; + $message->addressStreet = 'Main Street'; + $message->addressStreetNumber = '42'; + $message->addressPostcode = '1000'; + $message->addressExtra = 'Apartment 3B'; + $message->addressCity = 'Brussels'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + $household = $person->getCurrentHousehold($clock->now()); + self::assertNotNull($household); + $address = $household->getCurrentAddress(\DateTime::createFromImmutable($clock->now())); + self::assertNotNull($address); + self::assertEquals('Main Street', $address->getStreet()); + self::assertEquals('42', $address->getStreetNumber()); + self::assertSame($postalCode, $address->getPostcode()); + self::assertEquals('Apartment 3B', $address->getExtra()); + } + + public function testInvokeWithAllFields(): void + { + $personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class); + $identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class); + $entityManager = $this->prophesize(EntityManagerInterface::class); + $membersEditorFactory = $this->createMembersEditorFactoryMock(); + $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $genderRepository = $this->prophesize(GenderRepository::class); + $clock = new MockClock('2026-03-09'); + $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class); + $logger = new NullLogger(); + $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class); + $validator = $this->prophesize(ValidatorInterface::class); + + $definition = new PersonIdentifierDefinition(['fr' => 'dummy'], 'dummy'); + $personIdentifierDefinitionRepository->find(1)->willReturn($definition); + $identifierRepository->findByDefinitionAndCanonical($definition, '123')->willReturn([]); + + // Mock gender repository + $femaleGender = new Gender(); + $femaleGender->setGenderTranslation(GenderEnum::FEMALE); + $genderRepository->findByGenderTranslation(GenderEnum::FEMALE)->willReturn([$femaleGender]); + $genderRepository->findByGenderTranslation(Argument::any())->willReturn([$femaleGender]); + + // Mock center repository + $center = new \Chill\MainBundle\Entity\Center(); + $center->setName('Main Center'); + $centerRepository->findBy(['name' => 'Main Center'])->willReturn([$center]); + + // Mock postal code repository + $postalCode = new \Chill\MainBundle\Entity\PostalCode(); + $postalCode->setCode('1000'); + $postalCodeRepository->findOneBy(['code' => '1000'])->willReturn($postalCode); + + // Mock phone number parsing + $phoneNumber = $this->prophesize(\libphonenumber\PhoneNumber::class); + $mobileNumber = $this->prophesize(\libphonenumber\PhoneNumber::class); + $phoneNumberUtil->parse('+32123456789')->willReturn($phoneNumber->reveal()); + $phoneNumberUtil->parse('+32987654321')->willReturn($mobileNumber->reveal()); + + // Mock validator + $validator->validate(Argument::any())->willReturn(new ConstraintViolationList([])); + + // Mock person identifier manager - create real worker with mocked engine + $identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123'); + $worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker( + $identifierEngine->reveal(), + $definition + ); + $personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker); + + $person = null; + $entityManager->persist(Argument::that(function ($arg) use (&$person) { + if ($arg instanceof Person) { + $person = $arg; + + return true; + } + + return false; + }))->shouldBeCalled(); + $entityManager->persist(Argument::any())->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + $entityManager->clear()->shouldBeCalled(); + + $handler = new PersonUpsertHandler( + $personIdentifierDefinitionRepository->reveal(), + $identifierRepository->reveal(), + $entityManager->reveal(), + $membersEditorFactory, + $postalCodeRepository->reveal(), + $centerRepository->reveal(), + $genderRepository->reveal(), + $clock, + $phoneNumberUtil->reveal(), + $logger, + $personIdentifierManager->reveal(), + $validator->reveal(), + ); + + $message = new UpsertMessage(); + $message->externalId = '123'; + $message->personIdentifierDefinitionId = 1; + $message->firstName = 'Jane'; + $message->lastName = 'Smith'; + $message->gender = 'female'; + $message->birthdate = '1985-05-20'; + $message->phoneNumber = '+32123456789'; + $message->mobileNumber = '+32987654321'; + $message->addressStreet = 'Main Street'; + $message->addressStreetNumber = '42'; + $message->addressPostcode = '1000'; + $message->addressExtra = 'Apartment 3B'; + $message->addressCity = 'Brussels'; + $message->center = 'Main Center'; + + $handler->__invoke($message); + + self::assertInstanceOf(Person::class, $person); + self::assertEquals('Jane', $person->getFirstName()); + self::assertEquals('Smith', $person->getLastName()); + self::assertEquals(new \DateTime('1985-05-20'), $person->getBirthdate()); + self::assertSame($femaleGender, $person->getGender()); + self::assertSame($center, $person->getCenter()); + self::assertSame($phoneNumber->reveal(), $person->getPhonenumber()); + self::assertSame($mobileNumber->reveal(), $person->getMobilenumber()); + $household = $person->getCurrentHousehold($clock->now()); + self::assertNotNull($household); + $address = $household->getCurrentAddress(\DateTime::createFromImmutable($clock->now())); + self::assertNotNull($address); + self::assertEquals('Main Street', $address->getStreet()); + self::assertEquals('42', $address->getStreetNumber()); + self::assertSame($postalCode, $address->getPostcode()); + self::assertEquals('Apartment 3B', $address->getExtra()); + } +} diff --git a/src/Bundle/ChillTicketBundle/src/Resources/public/types.ts b/src/Bundle/ChillTicketBundle/src/Resources/public/types.ts index 80824d521..f82a53cdb 100644 --- a/src/Bundle/ChillTicketBundle/src/Resources/public/types.ts +++ b/src/Bundle/ChillTicketBundle/src/Resources/public/types.ts @@ -149,9 +149,8 @@ export type TicketHistoryLine = | CallerStateEvent; interface BaseTicket< - T extends - | "ticket_ticket:simple" - | "ticket_ticket:extended" = "ticket_ticket:simple", + T extends "ticket_ticket:simple" | "ticket_ticket:extended" = + "ticket_ticket:simple", > { type_extended: T; type: "ticket_ticket";