Commit 21b61fef authored by Stephane Bouvry's avatar Stephane Bouvry
Browse files

Liste des organisations / système de recherche V2 Fait (Test à réaliser)

parent 8485e099
Loading
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
# OSCAR 2.16.x "Connor"

## Contenu de cette mise à jour
 - Mail de jalon : Les jalons proposent une option pour envoyer un mail dédié aux personnes concernées
 - Jalon (modèle) : Enregistrement de "lots" de jalon préenregistrés permettant d'ajouter plusieurs jalons sur une activité en fonction d'une date de référence 
 - Refonte UI
 - ** Recherche / Liste des organisations**
   - Nouvelle interface
   - Ajout d'un critère "Utilisée"
   - Système de recherche en direct depuis l'index (plus rapide)
   - Refonte du *mapping* des données (méthode de référencement)
 - Refonte moteur de recherche Organisation
 - Refonte moteur de recherche activité

### Fonctionnel


### Technique


## Mise en place technique

Basculer sur la branche "connor"
+48 −2
Original line number Diff line number Diff line
@@ -110,17 +110,56 @@ class OrganizationController extends AbstractOscarController implements UseOrgan

    public function index2Action()
    {
        $types = $this->getOrganizationService()->getOrganizationTypesSelect();

        $sorting = [
            'hit'         => 'Pertinence (recherche textuelle)',
            'shortName'   => 'Nom court',
            'fullName'    => 'Nom long',
            'code'        => 'Code',
            'dateUpdated' => 'Date de mise à jour',
            'dateEnd'     => 'Date de fermeture',
            'dateCreated' => 'Date de création',
        ];

        $directions = [
            'ASC'  => "Croissant",
            'DESC' => "Décroissant"
        ];

        $urlShow = "";
        $urlEdit = "";
        $urlDelete = "";

        if( $this->getOscarUserContextService()->hasPrivileges(Privileges::ORGANIZATION_SHOW) ){
            $urlShow = $this->url()->fromRoute('organization/show', ['id'=>'']);
        }
        if( $this->getOscarUserContextService()->hasPrivileges(Privileges::ORGANIZATION_EDIT) ){
            $urlEdit = $this->url()->fromRoute('organization/edit', ['id'=>'']);
            $urlDelete = $this->url()->fromRoute('organization/delete', ['id'=>'']);
        }

        if( $this->isAjax() || $this->params()->fromQuery('f') === 'json'){
            //$this->getOscarUserContextService()->check("UNDEFINED_PRIVIEGE");
            try {
                $page = (int)$this->params()->fromQuery('page', 1);
                $search = $this->params()->fromQuery('q', '');
                $type = $this->params()->fromQuery('t', []);
                $type = $this->params()->fromQuery('t', '');
                $active = $this->params()->fromQuery('active', '');
                $sort = $this->params()->fromQuery('sort', 'hit');
                $direction = $this->params()->fromQuery('direction', 'ASC');
                $usage = $this->params()->fromQuery('usage', 'all');
                $rbp = $this->params()->fromQuery('rbp', 20);
                $view = new JsonModel();
                $filters = [];

                $filters['sort'] = $sort;
                $filters['active'] = $active;
                $filters['rbp'] = $rbp;
                $filters['type'] = $type ? explode(',', $type) : [];
                $filters['direction'] = $direction;
                $filters['usage'] = $usage;

                $result = $this->getOrganizationService()->searchOrganizations($search, $page, $filters);
                $view->setVariables($result);
            } catch (\Exception $exception) {
@@ -129,7 +168,14 @@ class OrganizationController extends AbstractOscarController implements UseOrgan

            return $view;
        }
        return [];
        return [
            'urlShow' => $urlShow,
            'urlEdit' => $urlEdit,
            'urlDelete' => $urlDelete,
            'types' => $types,
            'sorting' => $sorting,
            'directions' => $directions,
        ];
    }

    /**
+126 −52
Original line number Diff line number Diff line
@@ -132,7 +132,8 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
     * Retourne la liste des Roles disponible pour une organisation dans une activité.
     */
    public function getAvailableRolesOrganisationActivity(string $format = OscarFormatterConst::FORMAT_ARRAY_ID_OBJECT
    ): array {
    ): array
    {
        return OscarFormatterFactory::getFormatter($format)->format($this->getOrganizationRoleRepository()->findAll());
    }

@@ -313,8 +314,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana

        if ($rolePrincipaux == true) {
            $roles = $this->getOscarUserContext()->getRoleIdPrimary();
        }
        else {
        } else {
            $roles = $this->getOscarUserContext()->getRoleId();
        }

@@ -482,8 +482,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        if (!in_array($subStructure->getId(), $parentChildren) && $masterOrganizationId != $subOrganizationId) {
            $subStructure->setParent($parent);
            $this->getEntityManager()->flush($subStructure);
        }
        else {
        } else {
            throw new OscarException("L'affectation va provoquer une récurrence, opération annulée");
        }
    }
@@ -665,6 +664,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        $options = [];

        $types = $this->getEntityManager()->getRepository(OrganizationType::class)->findBy([], ['label' => 'ASC']);
        /** @var OrganizationType $type */
        foreach ($types as $type) {
            $options[$type->getId()] = (string)$type;
        }
@@ -723,7 +723,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        return new UnicaenDoctrinePaginator($qb, $page);
    }

    public function searchOrganizations(string $search, int $page, array $filter = []): array
    public function searchOrganizations(string $search, int $page, array $filters = []): array
    {
        $body = [
            '_source' => [
@@ -731,13 +731,90 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
            ],
            'query' => [
                'bool' => [
                    'filter' => [
                        [ 'term' => ['close' => false] ]
                    'filter' => []
                ]
            ]
        ];

        $filtersQuery = [];
        $size = 20;


        // Filtre actif/inactif
        if (array_key_exists('active', $filters) && $filters['active'] == '0' || $filters['active'] == '1') {
            $filtersQuery[] = ['term' => ['close' => $filters['active'] == '0']];
        }

        if (array_key_exists('sort', $filters) && $filters['sort'] !== 'hit') {
            $direction = 'DESC';
            if( $filters['direction'] && in_array($filters['direction'], ['ASC', 'DESC'])) {
                $direction = $filters['direction'];
            }
            $body["sort"] = [$filters['sort'] => ['order' => $direction, 'missing' => '_last']];

        }

        // Filtre actif/inactif
        if (array_key_exists('rbp', $filters)) {
            if( $filters['rbp'] <= 50 ) {
                $size = $filters['rbp'];
            }
        }

        // Filtre sur les types d'organisation
        if (array_key_exists('type', $filters)) {
            $types = $this->getOrganizationTypesSelect();
            $out = [];
            foreach ($types as $key=>$type) {
                if( in_array($key, $filters['type']) ){
                    $out[] = $type;
                }
            }
            if(count($out)){
                $filtersQuery[] = ['terms' => ['typeorg' => $out]];
            }
        }

        // USAGE
        if (array_key_exists('usage', $filters) && $filters['usage'] != 'all') {
            if( $filters['usage'] == 1 ){
                $filtersQuery[] = [
                    'bool' => [
                        'should' => [
                            [ "range" => ['activities_total' => [ "gt" => 0 ] ] ],
                            [ "range" => ['projects_total' => [ "gt" => 0 ] ] ],
                        ],
                        "minimum_should_match" => 1
                    ]
                ];
            } else {
                $filtersQuery[] = [
                    'bool' => [
                        'must' => [
                            [ "term" => ['activities_total' => 0 ] ],
                            [ "term" => ['projects_total' => 0 ] ],
                        ]
                    ]
                ];
            }
        }

        if( $page > 1 ){
            $from = ($size * ($page-1));
        } else {
            $from = 0;
        }

        $body['from'] = $from;
        $body['size'] = $size;


        $body['query']['bool']['filter'] = $filtersQuery;

        $dt = $this->getSearchEngineStrategy()->searchRaw($search, 10000, $body);
        $total = $dt['hits']['total']['value'];
        $pages = ceil($total/$size);

        $organizations = [];
        foreach ($dt['hits']['hits'] as $hit) {
            $dt = $hit['_source'];
@@ -745,7 +822,11 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
            $organizations[] = $dt;
        }
        return [
            'organizations' => $organizations
            'page' => $page,
            'total' => $total,
            'pages' => $pages,
            'organizations' => $organizations,
            'request' => $body,
        ];
    }

@@ -770,8 +851,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
                $connectorValue = $matches[2] . '%';
                $where = 'o.connectors LIKE \'%"' . $connectorName . '";s:%:"' . $connectorValue . '"%\'';
                $qb->orWhere($where);
            }
            else {
            } else {
                $qb
                    ->orWhere('LOWER(o.shortName) LIKE :search')
                    ->orWhere('LOWER(o.fullName) LIKE :search')
@@ -821,8 +901,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        if (isset($filter['active']) && $filter['active']) {
            if ($filter['active'] == 'ON') {
                $qb->andWhere('o.dateEnd IS NULL OR o.dateEnd > :now')->setParameter('now', new \DateTime());
            }
            else {
            } else {
                if ($filter['active'] == 'OFF') {
                    $qb->andWhere('o.dateEnd < :now')->setParameter('now', new \DateTime());
                }
@@ -953,8 +1032,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        if (isset($filter['active']) && $filter['active']) {
            if ($filter['active'] == 'ON') {
                $qb->andWhere('o.dateEnd IS NULL OR o.dateEnd > :now')->setParameter('now', new \DateTime());
            }
            else {
            } else {
                if ($filter['active'] == 'OFF') {
                    $qb->andWhere('o.dateEnd < :now')->setParameter('now', new \DateTime());
                }
@@ -1024,8 +1102,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
            }

            return $this->getOrganizationsByIds($ids);
        }
        else {
        } else {
            return $this->getSearchNativeQuery($search, [])->getQuery()->getResult();
        }
    }
@@ -1155,8 +1232,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        if ($id) {
            /** @var OrganizationType $urganizationType */
            $type = $this->getEntityManager()->getRepository(OrganizationType::class)->findOneBy(['id' => $id]);
        }
        else {
        } else {
            $type = new OrganizationType();
            $this->getEntityManager()->persist($type);
        }
@@ -1247,8 +1323,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
                    ->setEn($data['en'])
                    ->setFr($data['fr'])
                    ->setNumeric(intval($data['numeric']));
            }
            else {
            } else {
                $country = $exists[$data['alpha2']];
                $country->setAlpha2($data['alpha2'])
                    ->setAlpha3($data['alpha3'])
@@ -1326,8 +1401,7 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        $qb
            ->innerJoin('op.organization', 'o')
            ->where('o.id IN(:ids)')
            ->setParameter('ids', $ids_organization)
        ;
            ->setParameter('ids', $ids_organization);
        if ($roles != null) {
            $qb->innerJoin('op.roleObj', 'r')
                ->andWhere('r.roleId IN(:roles)')
+18 −16
Original line number Diff line number Diff line
@@ -125,6 +125,7 @@ abstract class ElasticSearchEngine
    public function rebuildIndex(array $items): void
    {
        $this->loggerService->debug('[elasticsearch] Rebuilding index "'. $this->getIndex().'"...');
        $this->loggerService->debug('[elasticsearch] item(s) '. count($items).'');
        try {
            $this->resetIndex();
        } catch (\Exception $exception) {
@@ -144,21 +145,23 @@ abstract class ElasticSearchEngine

                $params['body'][] = $this->getIndexableDatas($item);


                // On envoie par paquet de 1000
                if ($i % 1000 == 0) {
                    $this->loggerService->debug(" + BULK ");
                if ($i % 500 == 0) {
                    $this->loggerService->debug(" + BULK (" . count($params['body']). ") ");
                    $responses = $this->getClient()->bulk($params);

                    // clean datas
                    $params = ['body' => []];
                    $params['body'] = [];
                    unset($responses);
                }
            }

            if (!empty($params['body'])) {
                $this->loggerService->debug(" + BULK ");
                $this->loggerService->debug(" + BULK FINAL (" . count($params['body']). ") ");
                $client->bulk($params);
            }
            $this->loggerService->debug(" = $i item(s) indexed ");
        } catch (\Exception $exception) {
            $msg = "Réindexation impossible";
            $this->loggerService->critical("$msg : " . $exception->getMessage());
@@ -173,45 +176,45 @@ abstract class ElasticSearchEngine
     * @param int $limit
     * @return array
     */
    public function getParamsQuery(string $textSearch, int $limit = 10000, $customBody = null): array
    public function getParamsQuery(string $textSearch, int $limit = 10000, &$body = null): array
    {
        $search = trim($textSearch);

        if( $customBody == null ){
        if( $body == null ){
            $body = [
                'size' => $limit,
            ];
        } else {
            $body = $customBody;
        }
        if( array_key_exists('query', $body) ){
            $filter = $body['query']['bool']['filter'];
            if( $search ){
                $body['query'] = $this->getFieldsSearchedWeighted($search);
            } else {
                $body['query'] = ['bool' => []];
            }
            $body['query']['bool']['filter'] = $filter;
        }

        $query = [
            'index' => $this->getIndex(),
            'size' => 50,
            //'type'  => $this->getType(),
            'body'  => $body
        ];

        $this->loggerService->info("----query elastic : \n" . json_encode($query['body']['query']) ."\n---- ");

        return $query;
    }

    /**
     * @throws OscarException
     */
    public function searchRaw(string $search, int $limit = 10000, $body = null): array
    public function searchRaw(string $search, int $limit = 10000, &$body = []): array
    {
        $this->loggerService->info("Search '$search' in " . $this->getIndex());
        $client = $this->getClient();
        try {
            $params = $this->getParamsQuery($search, $limit, $body);
            $ids = $client->search($params);
            $body = $this->getParamsQuery($search, $limit, $body);
            $ids = $client->search($body);
            $this->loggerService->info("----query elastic : \n" . json_encode($body) ."\n---- ");
            return $ids;
        } catch (\Throwable $exception) {
            $ex = ElasticSearchEngineException::getInstance($exception);
@@ -285,7 +288,6 @@ abstract class ElasticSearchEngine
            ]
        ];

        error_log("REINDEX " . json_encode($params));
        try {
            return $this->getClient()->update($params);
        } catch (Missing404Exception $e) {
+7 −3
Original line number Diff line number Diff line
@@ -113,7 +113,7 @@ class OrganizationElasticSearch extends ElasticSearchEngine implements IOrganiza
            'email'       => $object->getEmail(),
            'close'        => $object->isClose(),
            'dateClose'        => $object->getDateEnd() ? $object->getDateEnd()->format('Y-m-d') : null,
            'type'          => "FOO",
            'typeorg'          => $object->getType(),
            'city'        => $object->getCity(),
            'country'     => $object->getCountry(),
            'zipcode'     => $object->getZipCode(),
@@ -123,6 +123,8 @@ class OrganizationElasticSearch extends ElasticSearchEngine implements IOrganiza
            'rnsr'       => $object->getRnsr(),
            'tvaintra'       => $object->getTvaintra(),
            'labintel'       => $object->getLabintel(),
            'dateCreated'       => $object->getDateCreatedStr(),
            'dateUpdated'       => $object->getDateUpdatedStr(),
            'persons'     => $persons,
            'persons_total'  => count($personsTotal),
            //'activities'  => array_values($activities),
@@ -199,7 +201,9 @@ class OrganizationElasticSearch extends ElasticSearchEngine implements IOrganiza
                    'id' => ['type' => 'keyword'],
                    'shortname' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'fullname' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'type' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'dateCreated' => ['type' => 'date'],
                    'dateUpdated' => ['type' => 'date'],
                    'typeorg' => ['type' => 'keyword'],
                    'description' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'email' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'city' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
@@ -209,7 +213,7 @@ class OrganizationElasticSearch extends ElasticSearchEngine implements IOrganiza
                    'rnsr' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'tvaintra' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'labintel' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
                    'connectors' => ['type' => 'object'],
                    'connectors' => ['type' => 'flattened'],
                    'close' => ['type' => 'boolean'],
                    'dateClose' => ['type' => 'date', 'format' => 'Y-m-d'],
                    'zipcode' => ['type' => 'text', 'analyzer' => 'folding_analyzer'],
Loading