Commit 429a726e authored by Stephane Bouvry's avatar Stephane Bouvry
Browse files

Ajout d'une option permettant d'exporter les organisations

parent 3028db50
Loading
Loading
Loading
Loading
Loading
+74 −140
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ use BjyAuthorize\Exception\UnAuthorizedException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\ORM\Query;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Oscar\Connector\IConnector;
use Oscar\Entity\Activity;
use Oscar\Entity\ActivityOrganization;
@@ -56,6 +57,7 @@ class OrganizationController extends AbstractOscarController implements UseOrgan

    /**
     * @param SessionService $sessionService
     * @return OrganizationController
     */
    public function setSessionService(SessionService $sessionService): self
    {
@@ -86,7 +88,8 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
                    );
                }
            }
        } else {
        }
        else {
            $token = $this->getSessionService()->createToken();
        }

@@ -100,6 +103,7 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
     * Liste des organisations.
     *
     * @return array
     * @throws \Exception
     */
    public function indexAction()
    {
@@ -111,8 +115,10 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
        if ($this->getOscarUserContextService()->hasPrivileges(Privileges::ORGANIZATION_SHOW)) {
            $allow = true;
            $justXHR = false;
        } else {
            $allow = $this->getOscarUserContextService()->hasOneOfPrivilegesInAnyRoles([
        }
        else {
            $allow = $this->getOscarUserContextService()
                ->hasOneOfPrivilegesInAnyRoles([
                                                   Privileges::ACTIVITY_ORGANIZATION_MANAGE,
                                                   Privileges::PROJECT_ORGANIZATION_MANAGE,
                                                   Privileges::ACTIVITY_INDEX,
@@ -136,13 +142,26 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
            'active' => $active,
        ];

        $organizations = [];

        if( $justXHR === false && $this->getRequest()->getQuery('action') == 'export-csv' ){
            $query = $this->getOrganizationService()->getSearchQuery($search, $filter);
            $organizations = $query->getQuery()->getResult();
            $this->getOrganizationService()->exportCsv($organizations);
            die();
        }

        try {
            $organizations = $this->getOrganizationService()->getOrganizationsSearchPaged($search, $page, $filter);
        } catch (BadRequest400Exception $e) {
            $error = _("Expression de recherche incorrecte") . ' : ' . $e->getMessage();
        } catch (NoNodesAvailableException $e) {
            $error = "Le moteur de recherche est introuvable";
        }




        if ($this->getRequest()->isXmlHttpRequest() || $this->params()->fromQuery('f') === 'json') {
            // test : return $this->getResponseUnauthorized("nop");
            $result = ['datas' => []];
@@ -169,101 +188,6 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
        );
    }

    /**
     * Liste des organisations.
     *
     * @return array
     */
    public function exportCsvAction()
    {
        $type = $this->params()->fromQuery('t', []);

        $filter = [
            'roles' => $this->params()->fromQuery('roles', []),
            'type' => $type
        ];

        $organizations = $this->getOrganizationService()->getOrganizations();

        // Fichier temporaire
        $filename = uniqid('oscar_export_organization_') . '.csv';
        $handler = fopen('/tmp/' . $filename, 'w');

        $headers = [
            'ID',
            'NomCourt',
            'NomLong',
            'Code',
            'Email',
            'URL',
            'rue1',
            'rue2',
            'rue3',
            'CP',
            'BP',
            'ville',
            'Pays',
            'CodePays',
            'Téléphone',
            'SIFAC',
            'SIRET',
            'Type',
            'TVA'
        ];

        fputcsv($handler, $headers);


        $i = 0;
        /** @var Organization $organization */
        foreach ($organizations as $organization) {
            $datas = [
                'ID' => $organization->getId(),
                'NomCourt' => $organization->getShortName(),
                'NomLong' => $organization->getFullName(),
                'Code' => $organization->getCode(),
                'Email' => $organization->getEmail(),
                'URL' => $organization->getUrl(),
                'rue1' => $organization->getStreet1(),
                'rue2' => $organization->getStreet2(),
                'rue3' => $organization->getStreet3(),
                'CP' => $organization->getZipCode(),
                'BP' => $organization->getBp(),
                'ville' => $organization->getCity(),
                'Pays' => $organization->getCountry(),
                'CodePays' => $organization->getCodePays(),
                'Téléphone' => $organization->getPhone(),
                'SIFAC' => $organization->getSifacId(),
                'SIRET' => $organization->getSiret(),
                'Type' => $organization->getType(),
                'TVA' => $organization->getNumTVACA(),
            ];
            $activities = [];
            /** @var ActivityOrganization $activity */
            foreach ($organization->getActivities() as $activity) {
                $activities[] = $activity->getActivity()->getOscarNum();
            }
            /** @var ProjectPartner $project */
            foreach ($organization->getProjects() as $project) {
                /** @var Activity $activity */
                foreach ($project->getProject()->getActivities() as $activity) {
                    $activities[] = $activity->getOscarNum();
                }
            }
            $datas['activities'] = implode(', ', $activities);

            fputcsv($handler, $datas);
        }

        fclose($handler);

        header('Content-Disposition: attachment; filename=oscar-export-organisations.csv');
        header('Content-Length: ' . filesize('/tmp/' . $filename));
        header('Content-type: plain/text');

        die(file_get_contents('/tmp/' . $filename));
    }

    /**
     * Retourne la liste des organisations recherchées.
     */
@@ -284,6 +208,7 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
        if (strlen($search) < 3) {
            return $this->getResponseBadRequest("Not enough chars (3 required)");
        }

        $organizations = $this->getOrganizationService()->getOrganizationsSearchPaged($search, $page);

        $result = ['datas' => []];
@@ -450,7 +375,9 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
                        );
                    }
                    $output['organizations'] = $subStructures;
                    $output['manage'] = $this->getOscarUserContextService()->hasPrivileges(Privileges::ORGANIZATION_EDIT);
                    $output['manage'] = $this->getOscarUserContextService()->hasPrivileges(
                        Privileges::ORGANIZATION_EDIT
                    );
                    return $this->jsonOutput($output);
                    break;

@@ -589,7 +516,8 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
            } catch (\Exception $e) {
                throw $e;
            }
        } else {
        }
        else {
            die('Bad connector ' . $connector);
        }
    }
@@ -634,7 +562,8 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
                    if (!isset($_SESSION['fusion_data'])) {
                        $this->flashMessenger()->addErrorMessage("Erreur de transmission des données.");
                        $this->redirect()->toRoute('organization/fusion');
                    } else {
                    }
                    else {
                        $organizationsFrom = $this->getEntityManager()->createQueryBuilder()
                            ->select('o')
                            ->from(Organization::class, 'o')
@@ -664,12 +593,14 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
                        $this->flashMessenger()->addSuccessMessage("Fusion des organisations réussie.");
                        $this->redirect()->toRoute('organization/show', ['id' => $to->getId()]);
                    }
                } else {
                }
                else {
                    $this->flashMessenger()->addErrorMessage("La procédure de fusion a été interrompue.");
                    $this->redirect()->toRoute('organization/fusion');
                    return;
                }
            } else {
            }
            else {
                $etape = 2;
                $hash = uniqid('fusion_');
                $_SESSION['fusion_hash'] = $hash;
@@ -766,7 +697,8 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
                    if (!isset($_SESSION['fusion_data'])) {
                        $this->flashMessenger()->addErrorMessage("Erreur de transmission des données.");
                        $this->redirect()->toRoute('organization/fusion');
                    } else {
                    }
                    else {
                        $organizationsFrom = $this->getEntityManager()->createQueryBuilder()
                            ->select('o')
                            ->from(Organization::class, 'o')
@@ -805,12 +737,14 @@ class OrganizationController extends AbstractOscarController implements UseOrgan
                        $this->flashMessenger()->addSuccessMessage("Fusion des organisations réussie.");
                        $this->redirect()->toRoute('organization/show', ['id' => $to->getId()]);
                    }
                } else {
                }
                else {
                    $this->flashMessenger()->addErrorMessage("La procédure de fusion a été interrompue.");
                    $this->redirect()->toRoute('organization/fusion');
                    return;
                }
            } else {
            }
            else {
                $etape = 2;
                $hash = uniqid('fusion_');
                $_SESSION['fusion_hash'] = $hash;
+93 −4
Original line number Diff line number Diff line
@@ -509,6 +509,94 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        return $idsOrganization;
    }

    public function exportCsv($organizations) :void
    {
        // Fichier temporaire
        $tmpDir = "/tmp";
        $filename = uniqid('oscar_export_organization_') . '.csv';
        $pathFileTmp = $tmpDir . '/' . $filename;
        $handler = fopen($pathFileTmp, 'w');

        if( !$handler ){
            $this->getLoggerService()->error("Export organisation : L'emplacement $filename n'est pas accessible en écriture.");
            throw new OscarException("Impossible d'exporter les données - Accès en écriture impossible");
        }

        $headers = [
            'ID',
            'NomCourt',
            'NomLong',
            'Code',
            'Email',
            'URL',
            'rue1',
            'rue2',
            'rue3',
            'CP',
            'BP',
            'ville',
            'Pays',
            'CodePays',
            'Téléphone',
            'SIFAC',
            'SIRET',
            'Type',
            'TVA'
        ];

        fputcsv($handler, $headers);


        $i = 0;
        /** @var Organization $organization */
        foreach ($organizations as $organization) {
            $datas = [
                'ID'        => $organization->getId(),
                'NomCourt'  => $organization->getShortName(),
                'NomLong'   => $organization->getFullName(),
                'Code'      => $organization->getCode(),
                'Email'     => $organization->getEmail(),
                'URL'       => $organization->getUrl(),
                'rue1'      => $organization->getStreet1(),
                'rue2'      => $organization->getStreet2(),
                'rue3'      => $organization->getStreet3(),
                'CP'        => $organization->getZipCode(),
                'BP'        => $organization->getBp(),
                'ville'     => $organization->getCity(),
                'Pays'      => $organization->getCountry(),
                'CodePays'  => $organization->getCodePays(),
                'Téléphone' => $organization->getPhone(),
                'SIFAC'     => $organization->getSifacId(),
                'SIRET'     => $organization->getSiret(),
                'Type'      => $organization->getType(),
                'TVA'       => $organization->getNumTVACA(),
            ];
//            $activities = [];
//            /** @var ActivityOrganization $activity */
//            foreach ($organization->getActivities() as $activity) {
//                $activities[] = $activity->getActivity()->getOscarNum();
//            }
//            /** @var ProjectPartner $project */
//            foreach ($organization->getProjects() as $project) {
//                /** @var Activity $activity */
//                foreach ($project->getProject()->getActivities() as $activity) {
//                    $activities[] = $activity->getOscarNum();
//                }
//            }
//            $datas['activities'] = implode(', ', $activities);

            fputcsv($handler, $datas);
        }

        fclose($handler);

        header('Content-Disposition: attachment; filename=oscar-export-organisations.csv');
        header('Content-Length: ' . filesize($pathFileTmp));
        header('Content-type: plain/text');

        die(file_get_contents($pathFileTmp));
    }


    public function getOrganizationTypes()
    {
@@ -587,7 +675,9 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
        if ($searchStrategy === null) {
            $opt = $this->getOscarConfigurationService()->getConfiguration('strategy.organization.search_engine');
            $class = new \ReflectionClass($opt['class']);
            $searchStrategy = $class->newInstanceArgs($opt['params']);
            $params = $opt['params'];
            $params[] = $this->getLoggerService();
            $searchStrategy = $class->newInstanceArgs($params);
        }
        return $searchStrategy;
    }
@@ -602,11 +692,10 @@ class OrganizationService implements UseOscarConfigurationService, UseEntityMana
     *
     * @param string $search
     *
     * @return Organization[]
     * @return UnicaenDoctrinePaginator
     */
    public function getOrganizationsSearchPaged($search, $page, $filter = [])
    public function getOrganizationsSearchPaged(string $search, int $page, array $filter = []) :UnicaenDoctrinePaginator
    {
        // $qb = $this->getSearchNativeQuery($search, $filter);
        $qb = $this->getSearchQuery($search, $filter);

        return new UnicaenDoctrinePaginator($qb, $page);
+3 −1
Original line number Diff line number Diff line
@@ -81,7 +81,9 @@ class PersonService implements UseOscarConfigurationService, UseEntityManager, U
        if ($searchStrategy === null) {
            $opt = $this->getOscarConfigurationService()->getConfiguration('strategy.person.search_engine');
            $class = new \ReflectionClass($opt['class']);
            $searchStrategy = $class->newInstanceArgs($opt['params']);
            $params = $opt['params'];
            $params[] = $this->getLoggerService();
            $searchStrategy = $class->newInstanceArgs($params);
        }
        return $searchStrategy;
    }
+3 −1
Original line number Diff line number Diff line
@@ -1936,8 +1936,10 @@ class ProjectGrantService implements UseGearmanJobLauncherService, UseOscarConfi
        static $searchStrategy;
        if ($searchStrategy === null) {
            $opt = $this->getOscarConfigurationService()->getConfiguration('strategy.activity.search_engine');
            $params = $opt['params'];
            $params[] = $this->getLoggerService();
            $class = new \ReflectionClass($opt['class']);
            $searchStrategy = $class->newInstanceArgs($opt['params']);
            $searchStrategy = $class->newInstanceArgs($params);
        }
        return $searchStrategy;
    }
+119 −73
Original line number Diff line number Diff line
@@ -5,46 +5,66 @@ namespace Oscar\Strategy\Search;
use Elasticsearch\Client;
use Elasticsearch\ClientBuilder;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Monolog\Logger;
use Oscar\Exception\OscarException;
use Oscar\Service\LoggerService;

abstract class ElasticSearchEngine
{
    private ?Client $elasticSearchClient = null;
    private array $hosts;

    private Logger $loggerService;

    /**
     * @param array $hosts
     * @param Logger $logger
     */
    public function __construct(array $hosts)
    public function __construct(array $hosts, Logger $logger)
    {
        $this->hosts = $hosts;
        $this->loggerService = $logger;
    }

    //
    abstract public function getIndexableDatas(mixed $object): array;

    abstract public function getFieldsSearchedWeighted(): array;

    abstract public function getIndex(): string;

    abstract public function getType(): string;

    /**
     * @return Client
     * @throws OscarException
     */
    protected function getClient(): Client
    {
        try {
            if ($this->elasticSearchClient === null) {
                $this->elasticSearchClient = ClientBuilder::create()
                    ->setHosts($this->getHosts())
                    ->build();
            }
            return $this->elasticSearchClient;
        } catch (\Exception $exception) {
            $msg = "Création du client impossible";
            $this->loggerService->critical("$msg : " . $exception->getMessage());
            throw new OscarException($msg);
        }
    }

    /**
     * @param mixed $object
     * @return callable|array
     * @throws OscarException
     */
    public function addItem(mixed $object): callable|array
    {
        $client = $this->getClient();

        try {
            $params = ['body' => []];

            $params['body'][] = [
@@ -57,17 +77,25 @@ abstract class ElasticSearchEngine

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

        return $this->getClient()->bulk($params);
            return $client->bulk($params);
        } catch (\Exception $exception) {
            $msg = "Impossible d'indexer l'information";
            $this->loggerService->critical("$msg : " . $exception->getMessage());
            throw new OscarException($msg);
        }
    }

    /**
     * @param array $items
     * @return void
     * @throws OscarException
     */
    public function rebuildIndex(array $items): void
    {
        $this->resetIndex();
        $client = $this->getClient();

        try {
            $i = 0;
            foreach ($items as $item) {
                $i++;
@@ -92,7 +120,12 @@ abstract class ElasticSearchEngine
            }

            if (!empty($params['body'])) {
            $this->getClient()->bulk($params);
                $client->bulk($params);
            }
        } catch (\Exception $exception) {
            $msg = "Réindexation impossible";
            $this->loggerService->critical("$msg : " . $exception->getMessage());
            throw new OscarException($msg);
        }
    }

@@ -100,6 +133,7 @@ abstract class ElasticSearchEngine
     * Retourne les paramètres utilisés pour la recherche.
     *
     * @param string $textSearch
     * @param int $limit
     * @return array
     */
    public function getParamsQuery(string $textSearch, int $limit = 10000): array
@@ -120,16 +154,24 @@ abstract class ElasticSearchEngine
        ];
    }

    /**
     * @throws OscarException
     */
    public function searchRaw(string $search, int $limit = 10000): array
    {
        $this->loggerService->info("Search '$search' in " . $this->getIndex());
        $client = $this->getClient();
        try {
            $search = trim($search);
            // TRAITEMENT de la recherche
            if (strpos($search, 'AND') || strpos($search, 'OR') || strpos($search, '"')) {
        } else {
            }
            else {
                $split = explode(" ", $search);
                if (count($split) == 1) {
                    $search = sprintf('%s OR %s', $split[0], $split[0] . '*');
            } else {
                }
                else {
                    $assembly = [];
                    foreach ($split as $term) {
                        $assembly[] = trim($term);
@@ -140,12 +182,19 @@ abstract class ElasticSearchEngine

            $params = $this->getParamsQuery($search, $limit);

        return $this->getClient()->search($params);
            return $client->search($params);

        } catch (\Exception $exception) {
            $msg = "Erreur de recherche";
            $this->loggerService->critical("$msg : " . $exception->getMessage());
            throw new OscarException($msg);
        }
    }

    /**
     * @param string $search
     * @return array Liste des IDs
     * @throws OscarException
     */
    public function search(string $search): array
    {
@@ -217,17 +266,14 @@ abstract class ElasticSearchEngine
            'index' => $this->getIndex()
        ];

        $client = $this->getClient();

        try {
            return $this->getClient()->indices()->delete($params);
        } catch (Missing404Exception $e) {
            return [];
            return $client->indices()->delete($params);
        } catch (\Exception $e) {
            throw new OscarException(
                sprintf(
                    "Impossible de réinitialiser l'index de recherche : '%s'",
                    $e->getMessage()
                )
            );
            $msg = "Impossible de réinitialiser le moteur de recherche";
            $this->loggerService->critical("$msg : " . $e->getMessage());
            throw new OscarException($msg);
        }
    }

Loading