diff --git a/CHANGELOG.md b/CHANGELOG.md index 875f8c34a..de73733d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to ## Unreleased + +* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection) +* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51) +* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184) * unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290) * [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36) * [address]: Display of incomplete address adjusted. @@ -18,6 +22,8 @@ and this project adheres to * add form to create/edit/delete relationship link, * improve graph refresh mechanism * add feature to export canvas as image (png) +* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern; + ## Test releases diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d2128437a..b88679016 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,16 +10,6 @@ parameters: count: 4 path: src/Bundle/ChillActivityBundle/Controller/ActivityController.php - - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 1 - path: src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php - - - - message: "#^Variable \\$activity might not be defined\\.$#" - count: 1 - path: src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -305,11 +295,6 @@ parameters: count: 1 path: src/Bundle/ChillDocStoreBundle/Controller/DocumentCategoryController.php - - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 1 - path: src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -325,11 +310,6 @@ parameters: count: 3 path: src/Bundle/ChillEventBundle/Controller/ParticipationController.php - - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" - count: 1 - path: src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 diff --git a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php index a23b2d73f..18a6f83d3 100644 --- a/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php +++ b/src/Bundle/ChillActivityBundle/DataFixtures/ORM/LoadActivity.php @@ -22,8 +22,10 @@ namespace Chill\ActivityBundle\DataFixtures\ORM; +use Chill\PersonBundle\Entity\Person; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; use Faker\Factory as FakerFactory; use Chill\ActivityBundle\Entity\Activity; @@ -31,25 +33,19 @@ use Chill\MainBundle\DataFixtures\ORM\LoadUsers; use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityReason; use Chill\ActivityBundle\DataFixtures\ORM\LoadActivityType; use Chill\MainBundle\DataFixtures\ORM\LoadScopes; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -/** - * Load reports into DB - * - * @author Champs-Libres Coop - */ -class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface +class LoadActivity extends AbstractFixture implements OrderedFixtureInterface { - use \Symfony\Component\DependencyInjection\ContainerAwareTrait; - /** * @var \Faker\Generator */ private $faker; + private EntityManagerInterface $em; - public function __construct() + public function __construct(EntityManagerInterface $em) { $this->faker = FakerFactory::create('fr_FR'); + $this->em = $em; } public function getOrder() @@ -88,7 +84,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, C { $reasonRef = LoadActivityReason::$references[array_rand(LoadActivityReason::$references)]; - if (in_array($this->getReference($reasonRef)->getId(), $excludingIds)) { + if (in_array($this->getReference($reasonRef)->getId(), $excludingIds, true)) { // we have a reason which should be excluded. Find another... return $this->getRandomActivityReason($excludingIds); } @@ -132,20 +128,17 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, C public function load(ObjectManager $manager) { - $persons = $this->container->get('doctrine.orm.entity_manager') - ->getRepository('ChillPersonBundle:Person') + $persons = $this->em + ->getRepository(Person::class) ->findAll(); - foreach($persons as $person) { + foreach ($persons as $person) { $activityNbr = rand(0,3); - $ref = 'activity_'.$person->getFullnameCanonical(); - for($i = 0; $i < $activityNbr; $i ++) { + for ($i = 0; $i < $activityNbr; $i ++) { $activity = $this->newRandomActivity($person); $manager->persist($activity); } - - $this->setReference($ref, $activity); } $manager->flush(); } diff --git a/src/Bundle/ChillActivityBundle/config/services/fixtures.yaml b/src/Bundle/ChillActivityBundle/config/services/fixtures.yaml index 4851b7838..900453a67 100644 --- a/src/Bundle/ChillActivityBundle/config/services/fixtures.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/fixtures.yaml @@ -1,4 +1,6 @@ services: Chill\ActivityBundle\DataFixtures\ORM\: + autowire: true + autoconfigure: true resource: ../../DataFixtures/ORM tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php index 0bf19418f..c23871c26 100644 --- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCalendarRange.php @@ -5,21 +5,20 @@ namespace Chill\CalendarBundle\DataFixtures\ORM; use Chill\CalendarBundle\Entity\CalendarRange; use Chill\MainBundle\DataFixtures\ORM\LoadUsers; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Repository\UserRepository; use DateTimeImmutable; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; -use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; class LoadCalendarRange extends Fixture implements FixtureGroupInterface, OrderedFixtureInterface { - public function __construct( - EntityManagerInterface $em + UserRepository $userRepository ) { - $this->userRepository = $em->getRepository(User::class); + $this->userRepository = $userRepository; } public function getOrder(): int @@ -37,7 +36,7 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere public function load(ObjectManager $manager): void { $arr = range(-50, 50); - + print "Creating calendar range ('plage de disponibilités')\n"; $users = $this->userRepository->findAll(); @@ -70,7 +69,7 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere ->setUser($u) ->setStartDate($startEvent) ->setEndDate($endEvent); - + $manager->persist($calendarRange); } @@ -79,4 +78,4 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere } $manager->flush(); } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php b/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php index 0f4577ffd..ec1773640 100644 --- a/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php +++ b/src/Bundle/ChillDocStoreBundle/DataFixtures/ORM/LoadDocumentACL.php @@ -38,7 +38,7 @@ class LoadDocumentACL extends AbstractFixture implements OrderedFixtureInterface return 35000; } - + public function load(ObjectManager $manager) { foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { @@ -57,15 +57,15 @@ class LoadDocumentACL extends AbstractFixture implements OrderedFixtureInterface break; case 'administrative': case 'direction': - if (in_array($scope->getName()['en'], array('administrative', 'social'))) { + if (in_array($scope->getName()['en'], array('administrative', 'social'), true)) { printf("denying power on %s\n", $scope->getName()['en']); break 2; // we do not want any power on social or administrative - } + } break; } - + printf("Adding Person report acl to %s " - . "permission group, scope '%s' \n", + . "permission group, scope '%s' \n", $permissionsGroup->getName(), $scope->getName()['en']); $roleScopeUpdate = (new RoleScope()) ->setRole(PersonDocumentVoter::CREATE) @@ -83,9 +83,9 @@ class LoadDocumentACL extends AbstractFixture implements OrderedFixtureInterface $manager->persist($roleScopeCreate); $manager->persist($roleScopeDelete); } - + } - + $manager->flush(); } diff --git a/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php b/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php index 7ef753e52..be95880e8 100644 --- a/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php +++ b/src/Bundle/ChillEventBundle/DataFixtures/ORM/LoadRolesACL.php @@ -50,19 +50,19 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface break; case 'administrative': case 'direction': - if (in_array($scope->getName()['en'], array('administrative', 'social'))) { + if (in_array($scope->getName()['en'], array('administrative', 'social'), true)) { break 2; // we do not want any power on social or administrative - } + } break; } - + printf("Adding CHILL_EVENT_UPDATE & CHILL_EVENT_CREATE " . "& CHILL_EVENT_PARTICIPATION_UPDATE & CHILL_EVENT_PARTICIPATION_CREATE " . "& CHILL_EVENT_SEE & CHILL_EVENT_SEE_DETAILS " . "to %s " - . "permission group, scope '%s' \n", + . "permission group, scope '%s' \n", $permissionsGroup->getName(), $scope->getName()['en']); - + $roleScopeUpdate = (new RoleScope()) ->setRole('CHILL_EVENT_UPDATE') ->setScope($scope); @@ -71,7 +71,7 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface ->setScope($scope); $permissionsGroup->addRoleScope($roleScopeUpdate); $permissionsGroup->addRoleScope($roleScopeUpdate2); - + $roleScopeCreate = (new RoleScope()) ->setRole('CHILL_EVENT_CREATE') ->setScope($scope); @@ -80,7 +80,7 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface ->setScope($scope); $permissionsGroup->addRoleScope($roleScopeCreate); $permissionsGroup->addRoleScope($roleScopeCreate2); - + $roleScopeSee = (new RoleScope()) ->setRole('CHILL_EVENT_SEE') ->setScope($scope); @@ -89,7 +89,7 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface ->setScope($scope); $permissionsGroup->addRoleScope($roleScopeSee); $permissionsGroup->addRoleScope($roleScopeSee2); - + $manager->persist($roleScopeUpdate); $manager->persist($roleScopeUpdate2); $manager->persist($roleScopeCreate); @@ -97,9 +97,9 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface $manager->persist($roleScopeSee); $manager->persist($roleScopeSee2); } - + } - + $manager->flush(); } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/mod_input_address_index.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/mod_input_address_index.js index 0f39c5e75..cb7da6e07 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/mod_input_address_index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/mod_input_address_index.js @@ -6,12 +6,12 @@ import App from './App.vue'; const i18n = _createI18n(addressMessages); const addAddressInput = (inputs) => { - + console.log(inputs) inputs.forEach(el => { let addressId = el.value, uniqid = el.dataset.inputAddress, - container = document.querySelector('div[data-input-address-container="' + uniqid + '"]'), + container = el.parentNode.querySelector('div[data-input-address-container="' + uniqid + '"]'), isEdit = addressId !== '', addressIdInt = addressId !== '' ? parseInt(addressId) : null ; diff --git a/src/Bundle/ChillMainBundle/Search/SearchApi.php b/src/Bundle/ChillMainBundle/Search/SearchApi.php index 0a7aa1737..877f1c330 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApi.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApi.php @@ -88,13 +88,13 @@ class SearchApi private function buildCountQuery(array $queries, $types, $parameters) { - $query = "SELECT COUNT(sq.key) AS count FROM ({union_unordered}) AS sq"; + $query = "SELECT COUNT(*) AS count FROM ({union_unordered}) AS sq"; $unions = []; $parameters = []; foreach ($queries as $q) { - $unions[] = $q->buildQuery(); - $parameters = \array_merge($parameters, $q->buildParameters()); + $unions[] = $q->buildQuery(true); + $parameters = \array_merge($parameters, $q->buildParameters(true)); } $unionUnordered = \implode(" UNION ", $unions); diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php index 9188c0b5c..095d49b43 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php @@ -76,33 +76,58 @@ class SearchApiQuery return $this; } - public function buildQuery(): string + public function buildQuery(bool $countOnly = false): string { - $where = \implode(' AND ', $this->whereClauses); + $isMultiple = count($this->whereClauses); + $where = + ($isMultiple ? '(' : ''). + \implode( + ($isMultiple ? ')' : '').' AND '.($isMultiple ? '(' : '') + , $this->whereClauses). + ($isMultiple ? ')' : '') + ; - return \strtr("SELECT + if (!$countOnly) { + $select = \strtr(" '{key}' AS key, {metadata} AS metadata, {pertinence} AS pertinence - FROM {from} - WHERE {where} + ", [ + '{key}' => $this->selectKey, + '{metadata}' => $this->jsonbMetadata, + '{pertinence}' => $this->pertinence, + ]); + } else { + $select = "1 AS c"; + } + + return \strtr("SELECT + {select} + FROM {from} + WHERE {where} ", [ - '{key}' => $this->selectKey, - '{metadata}' => $this->jsonbMetadata, - '{pertinence}' => $this->pertinence, + '{select}' => $select, '{from}' => $this->fromClause, '{where}' => $where, ]); } - public function buildParameters(): array + + public function buildParameters(bool $countOnly = false): array { - return \array_merge( - $this->selectKeyParams, - $this->jsonbMetadataParams, - $this->pertinenceParams, - $this->fromClauseParams, - \array_merge([], ...$this->whereClausesParams), - ); + if (!$countOnly) { + return \array_merge( + $this->selectKeyParams, + $this->jsonbMetadataParams, + $this->pertinenceParams, + $this->fromClauseParams, + \array_merge([], ...$this->whereClausesParams), + ); + } else { + return \array_merge( + $this->fromClauseParams, + \array_merge([], ...$this->whereClausesParams), + ); + } } } diff --git a/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php b/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php index 5df0b57dd..4440f2eef 100644 --- a/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php +++ b/src/Bundle/ChillMainBundle/Test/PrepareClientTrait.php @@ -30,7 +30,6 @@ trait PrepareClientTrait * * @param string $username the username (default 'center a_social') * @param string $password the password (default 'password') - * @return \Symfony\Component\BrowserKit\Client * @throws \LogicException */ public function getClientAuthenticated( diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php index 2e63f24e0..6aeb835e7 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php @@ -20,7 +20,12 @@ class SearchApiQueryTest extends TestCase $query = $q->buildQuery(); - $this->assertStringContainsString('foo AND bar', $query); + $this->assertStringContainsString('(foo) AND (bar)', $query); + $this->assertEquals(['alpha', 'beta'], $q->buildParameters()); + + $query = $q->buildQuery(true); + + $this->assertStringContainsString('(foo) AND (bar)', $query); $this->assertEquals(['alpha', 'beta'], $q->buildParameters()); } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php index c2a8f6e2c..0941dd7d4 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -28,14 +28,12 @@ use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository; use Symfony\Component\Workflow\Registry; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; -class AccompanyingCourseApiController extends ApiController +final class AccompanyingCourseApiController extends ApiController { - protected EventDispatcherInterface $eventDispatcher; - - protected ValidatorInterface $validator; - + private AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository; + private EventDispatcherInterface $eventDispatcher; + private ValidatorInterface $validator; private Registry $registry; - private ReferralsSuggestionInterface $referralAvailable; public function __construct( diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php index 11e41ba29..3f3e04cb3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdApiController.php @@ -47,13 +47,22 @@ class HouseholdApiController extends ApiController $count = $this->householdRepository->countByAccompanyingPeriodParticipation($person); $paginator = $this->getPaginatorFactory()->create($count); - if ($count === 0) { - $households = []; - } else { - $households = $this->householdRepository->findByAccompanyingPeriodParticipation($person, + $households = []; + if ($count !== 0) { + $allHouseholds = $this->householdRepository->findByAccompanyingPeriodParticipation($person, $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); - } + $currentHouseholdPerson = $person->getCurrentHousehold(); + foreach ($allHouseholds as $h) { + if ($h !== $currentHouseholdPerson) { + array_push($households, $h); + } + } + if (null !== $currentHouseholdPerson) { + $count = $count - 1; + $paginator = $this->getPaginatorFactory()->create($count); + } + } $collection = new Collection($households, $paginator); return $this->json($collection, Response::HTTP_OK, [], diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/Helper/PersonRandomHelper.php b/src/Bundle/ChillPersonBundle/DataFixtures/Helper/PersonRandomHelper.php new file mode 100644 index 000000000..0f7068751 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/Helper/PersonRandomHelper.php @@ -0,0 +1,40 @@ +countPersons) { + $qb = $em->createQueryBuilder(); + $this->countPersons = $qb->select('count(p)') + ->from(Person::class, 'p') + ->getQuery() + ->getSingleScalarResult() + ; + } + + if ([] === $this->randPersons) { + $qb = $em->createQueryBuilder(); + $this->randPersons = $qb + ->select('p') + ->from(Person::class, 'p') + ->getQuery() + ->setFirstResult(\random_int(0, $this->countPersons - $fetchBy)) + ->setMaxResults($fetchBy) + ->getResult() + ; + } + + return \array_pop($this->randPersons); + } + +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php index 35d11475c..df471aedd 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php @@ -262,7 +262,7 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con $manager->persist($accompanyingPeriod); echo "add person'".$person->__toString()."'\n"; - $this->addReference(self::PERSON, $person); + $this->addReference(self::PERSON.$person->getId(), $person); } private function getRandomUser(): User diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php index f433f2402..3b2b4518e 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelations.php @@ -10,7 +10,26 @@ use Doctrine\Persistence\ObjectManager; class LoadRelations extends Fixture implements FixtureGroupInterface { - public const RELATIONS = 'relations'; + public const RELATION_KEY = 'relations'; + public const RELATIONS = [ + ['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fille']], + ['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fils']], + ['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fille']], + ['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fils']], + + ['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Frère']], + ['title' => ['fr' => 'Soeur'], 'reverseTitle' => ['fr' => 'Soeur']], + ['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Soeur']], + + ['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-frère']], + ['title' => ['fr' => 'Demi-soeur'], 'reverseTitle' => ['fr' => 'Demi-soeur']], + ['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-soeur']], + + ['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Neveu']], + ['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Nièce']], + ['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Neveu']], + ['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Nièce']], + ]; public static function getGroups(): array { @@ -19,37 +38,17 @@ class LoadRelations extends Fixture implements FixtureGroupInterface public function load(ObjectManager $manager) { - $relations = [ - ['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fille']], - ['title' => ['fr' => 'Mère'], 'reverseTitle' => ['fr' => 'Fils']], - ['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fille']], - ['title' => ['fr' => 'Père'], 'reverseTitle' => ['fr' => 'Fils']], - - ['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Frère']], - ['title' => ['fr' => 'Soeur'], 'reverseTitle' => ['fr' => 'Soeur']], - ['title' => ['fr' => 'Frère'], 'reverseTitle' => ['fr' => 'Soeur']], - - ['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-frère']], - ['title' => ['fr' => 'Demi-soeur'], 'reverseTitle' => ['fr' => 'Demi-soeur']], - ['title' => ['fr' => 'Demi-frère'], 'reverseTitle' => ['fr' => 'Demi-soeur']], - - ['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Neveu']], - ['title' => ['fr' => 'Oncle'], 'reverseTitle' => ['fr' => 'Nièce']], - ['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Neveu']], - ['title' => ['fr' => 'Tante'], 'reverseTitle' => ['fr' => 'Nièce']], - ]; - - foreach($relations as $value){ + foreach (self::RELATIONS as $key => $value){ print "Creating a new relation type: relation" . $value['title']['fr'] . "reverse relation: " . $value['reverseTitle']['fr'] . "\n"; $relation = new Relation(); $relation->setTitle($value['title']) ->setReverseTitle($value['reverseTitle']); $manager->persist($relation); - $this->addReference(self::RELATIONS, $relation); + + $this->addReference(self::RELATION_KEY.$key, $relation); } $manager->flush(); - } } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php index 5efc68400..cce6d0365 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadRelationships.php @@ -3,17 +3,24 @@ declare(strict_types=1); namespace Chill\PersonBundle\DataFixtures\ORM; +use Chill\MainBundle\DataFixtures\ORM\LoadUsers; +use Chill\MainBundle\Entity\User; +use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; -use Chill\PersonBundle\DataFixtures\ORM\LoadPeople; -use Chill\PersonBundle\DataFixtures\ORM\LoadRelations; use Chill\PersonBundle\Entity\Relationships\Relationship; class LoadRelationships extends Fixture implements DependentFixtureInterface { + use PersonRandomHelper; + private EntityManagerInterface $em; - + public function __construct(EntityManagerInterface $em) + { + $this->em = $em; + } public function getDependencies() { @@ -21,16 +28,33 @@ class LoadRelationships extends Fixture implements DependentFixtureInterface LoadPeople::class, LoadRelations::class ]; - } public function load(ObjectManager $manager) { - $relationship = new Relationship; - $relationship->setFromPerson($this->getReference(LoadPeople::PERSON)); - $relationship->setToPerson($this->getReference(LoadPeople::PERSON)); - $relationship->setRelation($this->getReference(LoadRelations::RELATIONS)); - $relationship->setReverse((bool)random_int(0, 1)); + for ($i = 0; $i < 15; $i++) { + $user = $this->getRandomUser(); + $date = new \DateTimeImmutable(); + $relationship = (new Relationship()) + ->setFromPerson($this->getRandomPerson($this->em)) + ->setToPerson($this->getRandomPerson($this->em)) + ->setRelation($this->getReference(LoadRelations::RELATION_KEY. + \random_int(0, count(LoadRelations::RELATIONS) - 1))) + ->setReverse((bool) random_int(0, 1)) + ->setCreatedBy($user) + ->setUpdatedBy($user) + ->setCreatedAt($date) + ->setUpdatedAt($date) + ; + $manager->persist($relationship); + } + + $manager->flush(); } -} \ No newline at end of file + private function getRandomUser(): User + { + $userRef = array_rand(LoadUsers::$refs); + return $this->getReference($userRef); + } +} diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index 521d9a9d9..664bfe15d 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -144,7 +144,10 @@ class PersonType extends AbstractType } if ($this->config['phonenumber'] === 'visible') { - $builder->add('phonenumber', TelType::class, array('required' => false)); + $builder->add('phonenumber', TelType::class, array( + 'required' => false, + // 'placeholder' => '+33623124554' //TODO placeholder for phone numbers + )); } if ($this->config['mobilenumber'] === 'visible') { @@ -167,7 +170,8 @@ class PersonType extends AbstractType 'delete_empty' => function(PersonPhone $pp = null) { return NULL === $pp || $pp->isEmpty(); }, - 'error_bubbling' => false + 'error_bubbling' => false, + 'empty_collection_explain' => 'No additional phone numbers' ]); if ($this->config['email'] === 'visible') { diff --git a/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php index b735138f4..b68055581 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationRepository.php @@ -37,6 +37,6 @@ class RelationRepository implements ObjectRepository public function getClassName(): string { - return MaritalStatus::class; + return Relation::class; } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php index 3f105537a..7e5f2af24 100644 --- a/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/Relationships/RelationshipRepository.php @@ -10,7 +10,6 @@ use Doctrine\Persistence\ObjectRepository; class RelationshipRepository implements ObjectRepository { - private EntityRepository $repository; public function __construct(EntityManagerInterface $em) @@ -40,9 +39,9 @@ class RelationshipRepository implements ObjectRepository public function getClassName(): string { - return MaritalStatus::class; + return Relationship::class; } - + public function findByPerson($personId): array { // return all relationships of which person is part? or only where person is the fromPerson? @@ -56,5 +55,5 @@ class RelationshipRepository implements ObjectRepository ->getResult() ; } - + } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig index 0351d0ba1..781744bee 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig @@ -216,13 +216,23 @@ This view should receive those arguments: {%- if chill_person.fields.mobilenumber == 'visible' -%}
{{ 'Mobilenumber'|trans }} :
-
{% if person.mobilenumber is not empty %}
{{ person.mobilenumber|chill_format_phonenumber }}
{% else %}{{ 'No data given'|trans }}{% endif %}
+
{% if person.mobilenumber is not empty %}{{ person.mobilenumber|chill_format_phonenumber }}{% else %}{{ 'No data given'|trans }}{% endif %}
{% endif %} - {# TODO - display collection of others phonenumbers - #} + {%- if chill_person.fields.mobilenumber == 'visible' -%} + {% if person.otherPhoneNumbers is not empty %} +
+
{{ 'Others phone numbers'|trans }} :
+ {% for el in person.otherPhoneNumbers %} + {% if el.phonenumber is not empty %} +
{% if el.description is not empty %}{{ el.description }} : {% endif %}{{ el.phonenumber|chill_format_phonenumber }}
+ {% endif %} + {% endfor %} + +
+ {% endif %} + {% endif %} {%- if chill_person.fields.contact_info == 'visible' -%}
diff --git a/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php b/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php index dd0ae67c7..60122dbc2 100644 --- a/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php +++ b/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php @@ -2,29 +2,43 @@ namespace Chill\PersonBundle\Search; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Repository\PersonRepository; use Chill\MainBundle\Search\SearchApiQuery; use Chill\MainBundle\Search\SearchApiInterface; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Symfony\Component\Security\Core\Security; class SearchPersonApiProvider implements SearchApiInterface { private PersonRepository $personRepository; + private Security $security; + private AuthorizationHelperInterface $authorizationHelper; - public function __construct(PersonRepository $personRepository) + public function __construct(PersonRepository $personRepository, Security $security, AuthorizationHelperInterface $authorizationHelper) { $this->personRepository = $personRepository; + $this->security = $security; + $this->authorizationHelper = $authorizationHelper; } public function provideQuery(string $pattern, array $parameters): SearchApiQuery + { + return $this->addAuthorizations($this->buildBaseQuery($pattern, $parameters)); + } + + public function buildBaseQuery(string $pattern, array $parameters): SearchApiQuery { $query = new SearchApiQuery(); $query ->setSelectKey("person") ->setSelectJsonbMetadata("jsonb_build_object('id', person.id)") - ->setSelectPertinence("GREATEST(". - "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical), ". - "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int". - ")", [ $pattern, $pattern ]) + ->setSelectPertinence("". + "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) + ". + "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int + ". + "(EXISTS (SELECT 1 FROM unnest(string_to_array(fullnamecanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int" + , [ $pattern, $pattern, $pattern ]) ->setFromClause("chill_person_person AS person") ->setWhereClauses("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ". "person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' ", [ $pattern, $pattern ]) @@ -33,6 +47,28 @@ class SearchPersonApiProvider implements SearchApiInterface return $query; } + private function addAuthorizations(SearchApiQuery $query): SearchApiQuery + { + $authorizedCenters = $this->authorizationHelper + ->getReachableCenters($this->security->getUser(), PersonVoter::SEE); + + if ([] === $authorizedCenters) { + return $query->andWhereClause("FALSE = TRUE", []); + } + + return $query + ->andWhereClause( + strtr( + "person.center_id IN ({{ center_ids }})", + [ + '{{ center_ids }}' => \implode(', ', + \array_fill(0, count($authorizedCenters), '?')), + ] + ), + \array_map(function(Center $c) {return $c->getId();}, $authorizedCenters) + ); + } + public function supportsTypes(string $pattern, array $types, array $parameters): bool { return \in_array('person', $types); diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php index 6620a7c38..7e18c7c65 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php @@ -259,7 +259,8 @@ class PersonControllerUpdateTest extends WebTestCase return array( ['firstName', 'random Value', function(Person $person) { return $person->getFirstName(); } ], ['lastName' , 'random Value', function(Person $person) { return $person->getLastName(); } ], - ['placeOfBirth', 'NONE PLACE', function(Person $person) { return $person->getPlaceOfBirth(); }], + // reminder: this value is capitalized + ['placeOfBirth', 'A PLACE', function(Person $person) { return $person->getPlaceOfBirth(); }], ['birthdate', '1980-12-15', function(Person $person) { return $person->getBirthdate()->format('Y-m-d'); }], ['phonenumber', '+32123456789', function(Person $person) { return $person->getPhonenumber(); }], ['memo', 'jfkdlmq jkfldmsq jkmfdsq', function(Person $person) { return $person->getMemo(); }], diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php index 6d538a646..ac785f781 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php @@ -2,23 +2,32 @@ declare(strict_types=1); namespace Chill\PersonBundle\Tests\Controller; +use Chill\MainBundle\Test\PrepareClientTrait; +use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Entity\Relationships\Relation; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Component\HttpFoundation\Request; use Chill\PersonBundle\Repository\PersonRepository; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class RelationshipApiControllerTest extends WebTestCase { - public static function setUpBeforeClass() - { - static::bootKernel(); - } + use PrepareClientTrait; + + private KernelBrowser $client; + + /** + * A cache for all relations + * @var array|null|Relation[] + */ + private ?array $relations = null; public function setUp() { - $this->client = static::createClient(array(), array( - 'PHP_AUTH_USER' => 'fred', - 'PHP_AUTH_PW' => 'password', - )); + static::bootKernel(); + $this->client = $this->getClientAuthenticated(); } /** @@ -35,14 +44,13 @@ class RelationshipApiControllerTest extends WebTestCase /** * @dataProvider relationProvider */ - public function testPostRelationship($fromPersonId, $toPersonId, $relationId, $isReverse): void { - $this->client->request(Request::METHOD_POST, - '/api/1.0/person/relations/relationship.json', + $this->client->request(Request::METHOD_POST, + '/api/1.0/relations/relationship.json', + [], [], [], - [], \json_encode([ 'type' => 'relationship', 'fromPerson' => ['id' => $fromPersonId, 'type' => 'person'], @@ -57,18 +65,72 @@ class RelationshipApiControllerTest extends WebTestCase public function relationProvider(): array { - //TODO: which different cases to test? + static::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + $countPersons = $em->createQueryBuilder() + ->select('count(p)') + ->from(Person::class, 'p') + ->join('p.center', 'c') + ->where('c.name LIKE :name') + ->setParameter('name', 'Center A') + ->getQuery() + ->getSingleScalarResult() + ; + $persons = $em->createQueryBuilder() + ->select('p') + ->from(Person::class, 'p') + ->join('p.center', 'c') + ->where('c.name LIKE :name') + ->setParameter('name', 'Center A') + ->getQuery() + ->setMaxResults(2) + ->setFirstResult(\random_int(0, $countPersons - 1)) + ->getResult() + ; + return [ - [333, 334, 1, true], + [$persons[0]->getId(), $persons[1]->getId(), $this->getRandomRelation($em)->getId(), true], ]; } + private function getRandomRelation(EntityManagerInterface $em): Relation + { + if (null === $this->relations) { + $this->relations = $em->getRepository(Relation::class) + ->findAll(); + } + + return $this->relations[\array_rand($this->relations)]; + } + public function personProvider(): array { - //TODO: which different cases to test? + static::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + $countPersons = $em->createQueryBuilder() + ->select('count(p)') + ->from(Person::class, 'p') + ->join('p.center', 'c') + ->where('c.name LIKE :name') + ->setParameter('name', 'Center A') + ->getQuery() + ->getSingleScalarResult() + ; + $person = $em->createQueryBuilder() + ->select('p') + ->from(Person::class, 'p') + ->join('p.center', 'c') + ->where('c.name LIKE :name') + ->setParameter('name', 'Center A') + ->getQuery() + ->setMaxResults(1) + ->setFirstResult(\random_int(0, $countPersons - 1)) + ->getSingleResult() + ; + return [ - [333], + [$person->getId()], ]; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml b/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml index 72bf899f4..753521ad7 100644 --- a/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/fixtures.yaml @@ -1,6 +1,7 @@ services: Chill\PersonBundle\DataFixtures\ORM\: autowire: true + autoconfigure: true resource: ../../DataFixtures/ORM tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index ef8e58bb1..7f9527b91 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -50,6 +50,8 @@ mobilenumber: numéro de téléphone portable Accept short text message ?: La personne a donné l'autorisation d'utiliser ce no de téléphone pour l'envoi de rappel par SMS Accept short text message: La personne a donné l'autorisation d'utiliser ce no de téléphone pour l'envoi de rappel par SMS Other phonenumber: Autre numéro de téléphone +Others phone numbers: Autres numéros de téléphone +No additional phone numbers: Aucun numéro de téléphone supplémentaire Description: description Add new phone: Ajouter un numéro de téléphone Remove phone: Supprimer