浏览代码

complete a first version of the dolibarr sync command

Olivier Massot 3 年之前
父节点
当前提交
fc72fa7268

+ 8 - 0
src/Commands/DolibarrSyncCommand.php

@@ -48,9 +48,13 @@ class DolibarrSyncCommand extends Command
         }
 
         $output->writeln("Start the synchronization");
+        $t0 = microtime(true);
         $output->writeln("Scanning...");
 
         $operations = $this->dolibarrSyncService->scan();
+        $t1 = microtime(true);
+        $output->writeln("Scan lasted " . ($t1 - $t0) . " sec.");
+
         $output->writeln(count($operations) . " operations to be executed");
 
         if ($input->getOption('preview')) {
@@ -61,9 +65,13 @@ class DolibarrSyncCommand extends Command
                 }
             }
         } else {
+            $t0 = microtime(true);
             $output->writeln("Executing...");
 //            $operations = $this->dolibarrSyncService->execute($operations);
 //            $output->writeln(count($operations) . " operations successfully executed");
+
+            $t1 = microtime(true);
+            $output->writeln("Execution lasted " . ($t1 - $t0) . " sec.");
         }
 
         return Command::SUCCESS;

+ 34 - 28
src/Repository/Access/AccessRepository.php

@@ -116,40 +116,46 @@ class AccessRepository extends ServiceEntityRepository implements UserLoaderInte
     }
 
     /**
-     * Returns the current number of accesses with the given active role for the
-     * organization.
+     * Get all the currently active accesses and return an array
+     * of the form ['id' => $accessId, 'organization_id' => $organizationId, 'mission' => $mission]
      *
-     * @throws \Doctrine\ORM\NonUniqueResultException
-     * @throws \Doctrine\ORM\NoResultException
+     * Used by App\Service\Dolibarr\DolibarrSync\DolibarrSyncService
+     *
+     * @return array
      */
-    public function countAccessesWithActiveMission(int $organizationId, string $function): int {
-        $qb = $this->createQueryBuilder('a');
-        $qb->select('count(a.id)')
-            ->innerJoin('a.organizationFunction', 'ofn')
-            ->innerJoin('ofn.functionType', 'f')
-            ->innerJoin('a.organization', 'o')
-            ->where($qb->expr()->eq('f.mission', $qb->expr()->literal($function)))
-            ->andWhere($qb->expr()->eq('o.id', $organizationId));
-
-        DateConditions::addDateInPeriodCondition($qb, 'ofn', date('Y-m-d'));
-        $q = $qb->getQuery();
-        return (int)$q->getSingleResult($q::HYDRATE_SINGLE_SCALAR);
+    public function getAllActiveMembersAndMissions(): array
+    {
+        $qb = $this->createQueryBuilder('access');
+        $qb
+            ->select('access.id', 'organization.id as organization_id', 'function_type.mission')
+            ->innerJoin('access.organization', 'organization')
+            ->innerJoin('access.organizationFunction', 'organization_function')
+            ->innerJoin('organization_function.functionType', 'function_type')
+        ;
+        DateConditions::addDateInPeriodCondition($qb, 'organization_function', date('Y-m-d'));
+
+        return $qb->getQuery()->getArrayResult();
     }
 
     /**
-     * Returns the current number of accesses with an admin access to the
-     * organization.
+     * Returns the data needed
      *
-     * @throws \Doctrine\ORM\NonUniqueResultException
-     * @throws \Doctrine\ORM\NoResultException
+     * Used by App\Service\Dolibarr\DolibarrSync\DolibarrSyncService
+     *
+     * @param int $accessId
+     * @return array
      */
-    public function countAdminAccounts(int $organizationId): int {
-        $qb = $this->createQueryBuilder('a');
-        $qb->select('count(a.id)')
-            ->innerJoin('a.organization', 'o')
-            ->where($qb->expr()->eq('a.adminAccess', true))
-            ->andWhere($qb->expr()->eq('o.id', $organizationId));
-        $q = $qb->getQuery();
-        return (int)$q->getSingleResult($q::HYDRATE_SINGLE_SCALAR);
+    public function getDolibarrContactData(int $accessId): array
+    {
+        $qb = $this->createQueryBuilder('access');
+        $qb
+            ->select('access.id', 'organization.id as organization_id', 'function_type.mission')
+            ->innerJoin('access.organization', 'organization')
+            ->innerJoin('access.organizationFunction', 'organization_function')
+            ->innerJoin('organization_function.functionType', 'function_type')
+        ;
+        DateConditions::addDateInPeriodCondition($qb, 'organization_function', date('Y-m-d'));
+
+        return $qb->getQuery()->getArrayResult();
     }
 }

+ 1 - 0
src/Repository/Access/OrganizationFunctionRepository.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 namespace App\Repository\Access;
 
 use App\Entity\Access\OrganizationFunction;
+use App\Entity\Organization\Organization;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
 

+ 3 - 4
src/Service/ApiRequestService.php

@@ -4,9 +4,8 @@ declare(strict_types=1);
 namespace App\Service;
 
 use App\Service\Utils\UrlBuilder;
-use HttpException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
 use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
 use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
@@ -48,7 +47,7 @@ class ApiRequestService
         try {
             return $this->get($path, $parameters, $options)->getContent();
         } catch (ClientExceptionInterface | TransportExceptionInterface | RedirectionExceptionInterface | ServerExceptionInterface $e) {
-            throw new HttpException('data not found', $e, 404);
+            throw new HttpException(404, 'Data not found', $e);
         }
     }
 
@@ -130,7 +129,7 @@ class ApiRequestService
         try {
             return $this->client->request($method, $url, $options);
         } catch (HttpExceptionInterface | TransportExceptionInterface $e) {
-            throw new HttpException('Request error : ', $e, 500);
+            throw new HttpException(500, 'Request error : ', $e);
         }
     }
 }

+ 29 - 14
src/Service/Dolibarr/DolibarrApiService.php

@@ -4,11 +4,9 @@ declare(strict_types=1);
 namespace App\Service\Dolibarr;
 
 use App\Service\ApiRequestService;
-use HttpException;
 use JetBrains\PhpStorm\Pure;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Contracts\HttpClient\HttpClientInterface;
-use Symfony\Contracts\HttpClient\ResponseInterface;
 
 /**
  * Service d'appel à l'API dolibarr
@@ -31,7 +29,7 @@ class DolibarrApiService extends ApiRequestService
      * @throws HttpException
      */
     public function getSociety(int $organizationId): array {
-        return $this->getJsonContent("thirdparties" , [ "sqlfilters" => "ref_int=" . $organizationId])[0];
+        return $this->getJsonContent("thirdparties" , [ "limit" => "1", "sqlfilters" => "ref_int=" . $organizationId])[0];
     }
 
     /**
@@ -47,8 +45,11 @@ class DolibarrApiService extends ApiRequestService
                 ["limit" => "1", "sqlfilters" => "statut=1", "thirdparty_ids" => $socId]
             )[0];
         } catch (HttpException) {
-            // /!\ The dolibarr API will return a 404 error if no contract is found...
-            return null;
+            if ($e->getStatusCode() == 404) {
+                // /!\ The dolibarr API will return a 404 error if no results are found...
+                return [];
+            }
+            throw $e;
         }
     }
 
@@ -64,8 +65,11 @@ class DolibarrApiService extends ApiRequestService
                 "invoices",
                 ["sortfield" => "datef", "sortorder" => "DESC", "limit" => 5, "sqlfilters" => "fk_soc=" . $socId]);
         } catch (HttpException) {
-            // /!\ The dolibarr API will return a 404 error if no invoices are found...
-            return [];
+            if ($e->getStatusCode() == 404) {
+                // /!\ The dolibarr API will return a 404 error if no results are found...
+                return [];
+            }
+            throw $e;
         }
     }
 
@@ -77,17 +81,28 @@ class DolibarrApiService extends ApiRequestService
     {
         return $this->getJsonContent(
             "thirdparties",
-            ["sqlfilters" => "client=1"]);
+            ["sqlfilters" => "client=1", 'limit' => '1000000']);
     }
 
     /**
-     * Get all the contacts that have a non-null personId
+     * Get the society contacts that have a non-null personId
+     *
      * @throws HttpException
      */
-    public function getAllOpentalentContacts(): array
+    public function getOpentalentContacts(int $socId): array
     {
-        return $this->getJsonContent(
-            "contacts",
-            ["sqlfilters" => "(t.person_id:>:0)"]);
+        // On est obligé ici de passer la query en dur, sinon les parenthèses sont encodées,
+        // et dolibarr est pas content :(
+        try {
+            return $this->getJsonContent(
+                "contacts?limit=1000&thirdparty_ids=" . $socId . "&sqlfilters=(te.2iopen_person_id%3A%3E%3A0)"
+            );
+        } catch (HttpException $e) {
+            if ($e->getStatusCode() == 404) {
+                // /!\ The dolibarr API will return a 404 error if no results are found...
+                return [];
+            }
+            throw $e;
+        }
     }
 }

+ 20 - 14
src/Service/Dolibarr/DolibarrSync/DolibarrSyncOperation.php

@@ -124,23 +124,29 @@ class DolibarrSyncOperation
      */
     public function getChangeLog(): array {
         $messages = [];
-        foreach ($this->parameters as $field => $newValue) {
-            if (!array_key_exists($field, $this->currentData)) {
-                throw new \Exception('Field does not exists in the current object data : ' . $field);
-            }
-            if (is_array($newValue)) {
-                foreach ($newValue as $subField => $newSubValue) {
-                    if (!array_key_exists($subField, $this->currentData[$field])) {
-                        throw new \Exception('Field does not exists in the current object data : ' . $field . '.' . $subField);
+        if ($this->getMethod() == 'PUT') {
+            foreach ($this->parameters as $field => $newValue) {
+                if (!array_key_exists($field, $this->currentData)) {
+                    throw new \Exception('Field does not exists in the current object data : ' . $field);
+                }
+                if (is_array($newValue)) {
+                    foreach ($newValue as $subField => $newSubValue) {
+                        if (!array_key_exists($subField, $this->currentData[$field])) {
+                            throw new \Exception('Field does not exists in the current object data : ' . $field . '.' . $subField);
+                        }
+                        if ($newSubValue !== $this->currentData[$field][$subField]) {
+                            $messages[] = $field . '.' . $subField . ' : `'. $this->currentData[$field][$subField] . '` => `' . $newSubValue . '`';
+                        }
                     }
-                    if ($newSubValue !== $this->currentData[$field][$subField]) {
-                        $messages[] = $field . '.' . $subField . ' : `'. $this->currentData[$field][$subField] . '` => `' . $newSubValue . '`';
+                } else {
+                    if ($newValue !== $this->currentData[$field]) {
+                        $messages[] = $field . ' : `' . $this->currentData[$field] . '` => `' . $newValue . '`';
                     }
                 }
-            } else {
-                if ($newValue !== $this->currentData[$field]) {
-                    $messages[] = $field . ' : `' . $this->currentData[$field] . '` => `' . $newValue . '`';
-                }
+            }
+        } elseif ($this->getMethod() == 'POST') {
+            foreach ($this->parameters as $field => $newValue) {
+                $messages[] = $field . ' : ' . $newValue;
             }
         }
         return $messages;

+ 222 - 29
src/Service/Dolibarr/DolibarrSync/DolibarrSyncService.php

@@ -2,7 +2,6 @@
 
 namespace App\Service\Dolibarr\DolibarrSync;
 
-use App\Entity\Access\Access;
 use App\Entity\Core\AddressPostal;
 use App\Entity\Organization\Organization;
 use App\Enum\Access\FunctionEnum;
@@ -12,12 +11,10 @@ use App\Enum\Organization\AddressPostalOrganizationTypeEnum;
 use App\Enum\Organization\OrganizationIdsEnum;
 use App\Enum\Organization\SettingsProductEnum;
 use App\Repository\Access\AccessRepository;
+use App\Repository\Core\ContactPointRepository;
 use App\Repository\Organization\OrganizationRepository;
 use App\Service\Core\AddressPostalUtils;
 use App\Service\Dolibarr\DolibarrApiService;
-use Doctrine\ORM\EntityManager;
-use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\ORM\QueryBuilder;
 use Exception;
 use HttpException;
 use libphonenumber\PhoneNumber;
@@ -32,9 +29,11 @@ use libphonenumber\PhoneNumberUtil;
  */
 class DolibarrSyncService
 {
+
     public function __construct(
         private OrganizationRepository $organizationRepository,
         private AccessRepository $accessRepository,
+        private ContactPointRepository $contactPointRepository,
         private DolibarrApiService $dolibarrApiService,
     ) {}
 
@@ -54,8 +53,8 @@ class DolibarrSyncService
         // Index the dolibarr clients by organization ids
         $dolibarrClientsIndex = $this->getDolibarrSocietiesIndex();
 
-        // Index the dolibarr contacts by person ids
-        $dolibarrContactsIndex = $this->getDolibarrContactsIndex();
+        // Get all active accesses
+        $membersIndex = $this->getActiveMembersIndex();
 
         // Loop over the Opentalent organizations, and fill up the operations list
         $operations = [];
@@ -67,6 +66,13 @@ class DolibarrSyncService
 
             $dolibarrSociety = $dolibarrClientsIndex[$organization->getId()];
 
+            // Populate the expectedContacts array
+            if (array_key_exists($organization->getId(), $membersIndex)) {
+                $organizationMembers = $membersIndex[$organization->getId()];
+            } else {
+                $organizationMembers = [];
+            }
+
             $putSocietyData = [];
 
             // ** Sync name
@@ -134,9 +140,10 @@ class DolibarrSyncService
                 $product == SettingsProductEnum::SCHOOL()->getValue() ||
                 $product == SettingsProductEnum::SCHOOL_PREMIUM()->getValue()
             ) {
-                $studentsCount = $this->accessRepository->countAccessesWithActiveMission(
-                    $organization->getId(), FunctionEnum::STUDENT()->getValue()
-                );
+                $studentsCount = count(array_filter(
+                    $organizationMembers,
+                    function ($missions) { return in_array(FunctionEnum::STUDENT()->getValue(), $missions); }
+                ));
                 $infosArray[] = "Nombre d'élèves : " . $studentsCount;
             }
 
@@ -146,15 +153,19 @@ class DolibarrSyncService
                 $product == SettingsProductEnum::ARTIST()->getValue() ||
                 $product == SettingsProductEnum::ARTIST_PREMIUM()->getValue()
             ) {
-                $membersCount = $this->accessRepository->countAccessesWithActiveMission(
-                    $organization->getId(), FunctionEnum::ADHERENT()->getValue()
-                );
+                $membersCount = count(array_filter(
+                    $organizationMembers,
+                    function ($missions) { return in_array(FunctionEnum::ADHERENT()->getValue(), $missions); }
+                ));
                 $infosArray[] = "Nombre d'adhérents : " . $membersCount;
             }
 
-            $adminsCount = $this->accessRepository->countAdminAccounts(
-                $organization->getId()
-            );
+            $adminsCount = count($this->accessRepository->findBy(
+                [
+                    'organization' => $organization,
+                    'adminAccess' => true
+                ]
+            ));
             $infosArray[] = "Nombre d'accès admin : " . $adminsCount;
 
             $infos = implode('\n', $infosArray);
@@ -175,8 +186,164 @@ class DolibarrSyncService
             );
 
             // ** Contacts
+            $officeMissions = [
+                FunctionEnum::PRESIDENT()->getValue(),
+                FunctionEnum::SECRETARY()->getValue(),
+                FunctionEnum::TREASURER()->getValue(),
+                FunctionEnum::ADMINISTRATIVE_OFFICER()->getValue(),
+                FunctionEnum::ADMINISTRATIVE_SECRETARY()->getValue(),
+                FunctionEnum::ADMINISTRATIVE_DIRECTOR()->getValue(),
+                FunctionEnum::ADMINISTRATIVE_STAFF()->getValue()
+            ];
+
+            $rolesLabels =  [
+                'MISS' => [
+                    FunctionEnum::PRESIDENT()->getValue() => 'Présidente',
+                    FunctionEnum::SECRETARY()->getValue() => 'Secrétaire',
+                    FunctionEnum::TREASURER()->getValue() => 'Trésorière',
+                    FunctionEnum::ADMINISTRATIVE_OFFICER()->getValue() => 'Responsable admin.',
+                    FunctionEnum::ADMINISTRATIVE_SECRETARY()->getValue() => 'Secrétaire admin.',
+                    FunctionEnum::ADMINISTRATIVE_DIRECTOR()->getValue() => 'Directrice',
+                    FunctionEnum::ADMINISTRATIVE_STAFF()->getValue() => 'Personnel administratif'
+                ],
+                'MISTER' => [
+                    FunctionEnum::PRESIDENT()->getValue() => 'Président',
+                    FunctionEnum::SECRETARY()->getValue() => 'Secrétaire',
+                    FunctionEnum::TREASURER()->getValue() => 'Trésorier',
+                    FunctionEnum::ADMINISTRATIVE_OFFICER()->getValue() => 'Responsable admin.',
+                    FunctionEnum::ADMINISTRATIVE_SECRETARY()->getValue() => 'Secrétaire admin.',
+                    FunctionEnum::ADMINISTRATIVE_DIRECTOR()->getValue() => 'Directeur',
+                    FunctionEnum::ADMINISTRATIVE_STAFF()->getValue() => 'Personnel administratif'
+                ]
+            ];
+
+            $dolibarrContactsIndex = $this->getDolibarrContactsIndex($dolibarrSociety['id']);
+            $contactsProcessed = [];
+
+            foreach ($organizationMembers as $accessId => $missions) {
+                foreach ($missions as $mission) {
+                    if (in_array($mission, $officeMissions)) {
+                        $access = $this->accessRepository->find($accessId);
+                        $person = $access->getPerson();
+
+                        if (in_array($person->getId(), $contactsProcessed)) {
+                            // already updated from another mission
+                            continue;
+                        }
+                        $contactsProcessed[] = $person->getId();
+
+                        if (array_key_exists($person->getId(), $dolibarrContactsIndex)) {
+                            $dolibarrContact = $dolibarrContactsIndex[$person->getId()];
+                        } else {
+                            // new contact
+                            $dolibarrContact = null;
+                        }
+
+                        $putContactData = [];
+
+                        $contactRes = $this->contactPointRepository->getByTypeAndPerson(
+                            ContactPointTypeEnum::PRINCIPAL()->getValue(), $person
+                        );
+                        if (empty($contactRes)) {
+                            $contactRes = $this->contactPointRepository->getByTypeAndPerson(
+                                ContactPointTypeEnum::OTHER()->getValue(), $person
+                            );
+                        }
+                        $contact = empty($contactRes) ? null : $contactRes[0];
+
+                        $lastname = $person->getName();
+                        $firstname = $person->getGivenName();
+                        $email = $contact?->getEmail();
+                        $phone = null;
+                        if ($contact !== null && $contact->getTelphone() !== null) {
+                            $phone = $this->formatPhoneNumber($contact->getTelphone());
+                        }
+                        $mobilePhone = null;
+                        if ($contact !== null && $contact->getMobilPhone() !== null) {
+                            $mobilePhone = $this->formatPhoneNumber($contact->getMobilPhone());
+                        }
+                        $civility = $person->getGender() === 'MISS' ? 'Mrs.' : 'Mr.';
+                        $poste = implode(
+                            ', ',
+                            array_map(
+                                function($m) use ($rolesLabels, $person) {
+                                    return $rolesLabels[$person->getGender() ?? 'MISTER'][$m];
+                                    },
+                                array_filter(
+                                    $missions,
+                                    function($m) use ($officeMissions){ return in_array($m, $officeMissions); }
+                                )
+                            )
+                        );
+
+                        if ($dolibarrContact === null) {
+                            $postContactData = [
+                                'civility' => $civility,
+                                'lastname' => $lastname,
+                                'firstname' => $firstname,
+                                'email' => $email,
+                                'phone_pro' => $phone,
+                                'phone_mobile' => $mobilePhone,
+                                'poste' => $poste
+                            ];
+
+                            $operations[] = new DolibarrSyncOperation(
+                                'Create person ' . $person->getId() . ' - ' . $person->getName() . ' ' . $person->getGivenName(),
+                                'POST',
+                                'contacts',
+                                $postContactData
+                            );
+                        } else {
+                            if ($civility !== $dolibarrContact['civility']) {
+                                $putContactData['civility'] = $civility;
+                            }
+                            if ($lastname !== $dolibarrContact['lastname']) {
+                                $putContactData['lastname'] = $lastname;
+                            }
+                            if ($firstname !== $dolibarrContact['firstname']) {
+                                $putContactData['firstname'] = $firstname;
+                            }
+                            if ($email !== $dolibarrContact['email']) {
+                                $putContactData['email'] = $contact->getEmail();
+                            }
+                            // the dolibarr api return an empty string even if the field is null
+                            if ($phone !== $dolibarrContact['phone_pro'] && $dolibarrContact['phone_pro'] !== '') {
+                                $putContactData['phone_pro'] = $phone;
+                            }
+                            // the dolibarr api return an empty string even if the field is null
+                            if ($mobilePhone !== $dolibarrContact['phone_mobile'] && $dolibarrContact['phone_mobile'] !== '') {
+                                $putContactData['phone_mobile'] = $mobilePhone;
+                            }
+                            if ($poste !== $dolibarrContact['poste']) {
+                                $putContactData['poste'] = $poste;
+                            }
+
+                            $operations[] = new DolibarrSyncOperation(
+                                'Update person ' . $person->getId() . ' - ' . $person->getName() . ' ' . $person->getGivenName(),
+                                'PUT',
+                                'contacts/' . $dolibarrContact['id'],
+                                $putContactData,
+                                $dolibarrContact
+                            );
+                        }
+
+                        // No need to test the other missions of this access
+                        break;
+                    }
+                }
+            }
 
-
+            foreach ($dolibarrContactsIndex as $personId => $contactData) {
+                if (!in_array($personId, $contactsProcessed)) {
+                    // Ce personId n'existe plus dans les membres Opentalent de cette société, on delete
+                    $operations[] = new DolibarrSyncOperation(
+                        'Delete person ' . $personId . ' - ' . $person->getName() . ' ' . $person->getGivenName() .
+                        ' from the contacts of organization ' . $organization->getId() . ' - ' . $organization->getName(),
+                        'DELETE',
+                        'contacts/' . $contactData['id']
+                    );
+                }
+            }
         }
 
         return $operations;
@@ -229,7 +396,7 @@ class DolibarrSyncService
     }
 
     /**
-     * Get the client societies dolibarr and index it by organization id
+     * Get the client societies dolibarr and index them by organization id
      *
      * @return array An index of the form [$organizationId => $dolibarrData]
      * @throws HttpException
@@ -250,19 +417,44 @@ class DolibarrSyncService
         return $index;
     }
 
-    private function getDolibarrContactsIndex(): array {
+    /**
+     * Get the dolibarr contacts of the society and index them by person_id
+     *
+     * @return array An index of the form [$personId => $dolibarrData]
+     */
+    private function getDolibarrContactsIndex(int $socId): array {
         $index = [];
-        foreach ($this->dolibarrApiService->getAllOpentalentContacts() as $contactData) {
-            if (!$contactData["array_options"]["options_2iopen_person_id"] > 0) {
-                $this->logScanError(
-                    "Missing person id : " . $contactData["lastname"] . " " . $contactData["firstname"] . "(id:" . $contactData["id"] .")"
-                );
-                continue;
-            }
-
-            $index[$contactData["array_options"]["options_2iopen_person_id"]] = $contactData;
+        $contacts = $this->dolibarrApiService->getOpentalentContacts($socId);
+        foreach ($contacts as $contactData) {
+            $personId = intval($contactData["array_options"]["options_2iopen_person_id"]);
+            $index[$personId] = $contactData;
         }
+        return $index;
+    }
 
+    /**
+     * Returns an index of all the active members with their current mission(s)
+     *
+     * Index is the form: [$organizationId => [$accessId => [$mission, $mission...], $accessId...], $organizationId2...]
+     *
+     * @return array
+     */
+    private function getActiveMembersIndex(): array {
+        $index = [];
+        $results = $this->accessRepository->getAllActiveMembersAndMissions();
+        foreach ($results as $row) {
+            $accessId = $row['id'];
+            $organizationId = $row['organization_id'];
+            $mission = $row['mission'];
+
+            if (!array_key_exists($organizationId, $index)) {
+                $index[$organizationId] = [];
+            }
+            if (!array_key_exists($accessId, $index[$organizationId])) {
+                $index[$organizationId][$accessId] = [];
+            }
+            $index[$organizationId][$accessId][] = $mission;
+        }
         return $index;
     }
 
@@ -295,9 +487,10 @@ class DolibarrSyncService
      * Retrieve the phone for the organization
      *
      * @param Organization $organization
-     * @return null
+     * @return string|null
      */
-    private function getOrganizationPhone(Organization $organization) {
+    private function getOrganizationPhone(Organization $organization): ?string
+    {
         $contactPriorities = [
             ContactPointTypeEnum::BILL()->getValue(),
             ContactPointTypeEnum::CONTACT()->getValue(),