From 8c500ec470841949f0ffe836e2815bff05167421 Mon Sep 17 00:00:00 2001
From: Guillaume Heurtier <guillaume.heurtier@maarch.org>
Date: Thu, 6 Aug 2020 18:20:56 +0200
Subject: [PATCH] FEAT #14459 TIME 10:40 export entities

---
 rest/index.php                                |   1 +
 .../entity/controllers/EntityController.php   | 130 +++++++++++++++++-
 .../administration/administration.module.ts   |   7 +-
 .../entities-administration.component.html    |   8 +-
 .../entities-administration.component.ts      |   6 +
 .../export/entities-export.component.html     |  37 +++++
 .../export/entities-export.component.scss     |  26 ++++
 .../export/entities-export.component.ts       |  92 +++++++++++++
 8 files changed, 302 insertions(+), 5 deletions(-)
 create mode 100644 src/frontend/app/administration/entity/export/entities-export.component.html
 create mode 100644 src/frontend/app/administration/entity/export/entities-export.component.scss
 create mode 100644 src/frontend/app/administration/entity/export/entities-export.component.ts

diff --git a/rest/index.php b/rest/index.php
index 713a079b23b..3a753ef8bef 100755
--- a/rest/index.php
+++ b/rest/index.php
@@ -223,6 +223,7 @@ $app->delete('/emails/{id}', \Email\controllers\EmailController::class . ':delet
 
 //Entities
 $app->get('/entities', \Entity\controllers\EntityController::class . ':get');
+$app->put('/entities/export', \Entity\controllers\EntityController::class . ':export');
 $app->post('/entities', \Entity\controllers\EntityController::class . ':create');
 $app->get('/entities/{id}', \Entity\controllers\EntityController::class . ':getById');
 $app->put('/entities/{id}', \Entity\controllers\EntityController::class . ':update');
diff --git a/src/app/entity/controllers/EntityController.php b/src/app/entity/controllers/EntityController.php
index 3da13c37e98..dbbee521349 100755
--- a/src/app/entity/controllers/EntityController.php
+++ b/src/app/entity/controllers/EntityController.php
@@ -28,9 +28,7 @@ use Resource\models\ResModel;
 use Respect\Validation\Validator;
 use Slim\Http\Request;
 use Slim\Http\Response;
-use SrcCore\models\PasswordModel;
 use Template\models\TemplateAssociationModel;
-use User\controllers\UserController;
 use User\models\UserEntityModel;
 use User\models\UserModel;
 use \Template\models\TemplateModel;
@@ -523,4 +521,132 @@ class EntityController
     {
         return $response->withJson(['types' => EntityModel::getTypes()]);
     }
+
+    public function export(Request $request, Response $response)
+    {
+        if (!PrivilegeController::hasPrivilege(['privilegeId' => 'manage_entities', 'userId' => $GLOBALS['id']])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Service forbidden']);
+        }
+
+        $body = $request->getParsedBody();
+
+        $delimiter = ';';
+        if (!empty($body['delimiter'])) {
+            if (in_array($body['delimiter'], [',', ';', 'TAB'])) {
+                $delimiter = ($body['delimiter'] == 'TAB' ? "\t" : $body['delimiter']);
+            }
+        }
+
+        $fields = [
+            'id', 'entity_id', 'entity_label', 'short_label', 'entity_full_name', 'enabled', 'adrs_1', 'adrs_2', 'adrs_3', 'zipcode', 'city',
+            'country', 'email', 'parent_entity_id', 'entity_type', 'business_id', 'ldap_id', 'archival_agency', 'archival_agreement',
+            'folder_import', 'external_id',
+        ];
+
+        $csvHead = array_merge($fields, [ 'diffusionList', 'visaCircuit', 'opinionCircuit', 'users', 'templates']);
+
+        ini_set('memory_limit', -1);
+
+        $file = fopen('php://temp', 'w');
+        $delimiter = ($delimiter == 'TAB' ? "\t" : $delimiter);
+
+        $entities = EntityModel::getAllowedEntitiesByUserId(['userId' => $GLOBALS['login']]);
+        $entities = array_filter($entities, function ($entity) {
+            return $entity['allowed'] == true;
+        });
+        $entitiesIds = array_column($entities, 'serialId');
+        $entities = EntityModel::get([
+            'select'  => $fields,
+            'where'   => ['id in (?)'],
+            'data'    => [$entitiesIds],
+            'orderBy' => ['parent_entity_id', 'entity_label']
+        ]);
+
+        $templateType = ['diffusionList', 'visaCircuit', 'opinionCircuit'];
+
+        foreach ($entities as $key => $entity) {
+            // list templates
+            foreach ($templateType as $type) {
+                $template = ListTemplateModel::get([
+                    'select' => ['*'],
+                    'where'  => ['entity_id = ?', 'type = ?'],
+                    'data'   => [$entity['id'], $type]
+                ]);
+
+                $list = [];
+                if (!empty($template)) {
+                    $template = $template[0];
+                    $templateItems = ListTemplateItemModel::get([
+                        'select'  => ['*'],
+                        'where'   => ['list_template_id = ?'],
+                        'data'    => [$template['id']],
+                        'orderBy' => ['sequence']
+                    ]);
+                    foreach ($templateItems as $templateItem) {
+                        $item = [];
+                        if ($templateItem['item_mode'] == 'dest') {
+                            $item[] = _ASSIGNEE;
+                        } elseif ($templateItem['item_mode'] == 'cc') {
+                            $item[] = _TO_CC;
+                        } elseif ($templateItem['item_mode'] == 'avis') {
+                            $item[] = _AVIS_USER;
+                        } elseif ($templateItem['item_mode'] == 'avis_copy') {
+                            $item[] = _AVIS_USER_COPY;
+                        } elseif ($templateItem['item_mode'] == 'visa') {
+                            $item[] = _VISA_USER_MIN;
+                        } elseif ($templateItem['item_mode'] == 'sign') {
+                            $item[] = _SIGNATORY;
+                        }
+
+                        if ($templateItem['item_type'] == 'user') {
+                            $item[] = UserModel::getLabelledUserById(['id' => $templateItem['item_id']]);
+                        } elseif ($templateItem['item_type'] == 'entity') {
+                            $entityLabel = EntityModel::getById(['select' => ['entity_label'], 'id' => $templateItem['item_id']]);
+                            $item[] = $entityLabel['entity_label'];
+                        }
+
+                        $list[] = implode(' ', $item);
+                    }
+                }
+                $entities[$key][] = implode("\n", $list);
+            }
+
+            // Users in entity
+            $users = UserEntityModel::getWithUsers([
+                'select'    => ['DISTINCT users.id', 'firstname', 'lastname'],
+                'where'     => ['users_entities.entity_id = ?'],
+                'data'      => [$entity['entity_id']]
+            ]);
+            $users = array_map(function ($user) {
+                return $user['firstname'] . ' ' . $user['lastname'];
+            }, $users);
+            $entities[$key][] = implode("\n", $users);
+
+
+            // Document templates
+            $templates = TemplateModel::getByEntity([
+                'select'    => ['t.template_label', 't.template_target'],
+                'entities'  => [$entity['entity_id']]
+            ]);
+            $templates = array_map(function ($template) {
+                return $template['template_label'] . ' ' . $template['template_target'];
+            }, $templates);
+            $entities[$key][] = implode("\n", $templates);
+        }
+
+        fputcsv($file, $csvHead, $delimiter);
+
+        foreach ($entities as $entity) {
+            fputcsv($file, $entity, $delimiter);
+        }
+
+        rewind($file);
+
+        $response->write(stream_get_contents($file));
+        $response = $response->withAddedHeader('Content-Disposition', 'attachment; filename=export_maarch.csv');
+        $contentType = 'application/vnd.ms-excel';
+        fclose($file);
+
+        return $response->withHeader('Content-Type', $contentType);
+    }
 }
diff --git a/src/frontend/app/administration/administration.module.ts b/src/frontend/app/administration/administration.module.ts
index 22f45d55a54..6067699428d 100755
--- a/src/frontend/app/administration/administration.module.ts
+++ b/src/frontend/app/administration/administration.module.ts
@@ -69,6 +69,7 @@ import { UsersAdministrationComponent, UsersAdministrationRedirectModalComponent
 import { UsersImportComponent } from './user/import/users-import.component';
 import { UsersExportComponent } from './user/export/users-export.component';
 import { TranslateService } from '@ngx-translate/core';
+import {EntitiesExportComponent} from './entity/export/entities-export.component';
 
 
 @NgModule({
@@ -145,7 +146,8 @@ import { TranslateService } from '@ngx-translate/core';
         UsersAdministrationComponent,
         UsersAdministrationRedirectModalComponent,
         UsersImportComponent,
-        UsersExportComponent
+        UsersExportComponent,
+        EntitiesExportComponent
     ],
     entryComponents: [
         AccountLinkComponent,
@@ -162,7 +164,8 @@ import { TranslateService } from '@ngx-translate/core';
         UserAdministrationRedirectModalComponent,
         UsersAdministrationRedirectModalComponent,
         UsersImportComponent,
-        UsersExportComponent
+        UsersExportComponent,
+        EntitiesExportComponent
     ],
     providers: [
         AdministrationService
diff --git a/src/frontend/app/administration/entity/entities-administration.component.html b/src/frontend/app/administration/entity/entities-administration.component.html
index 7f2131ed747..e46997b25a1 100755
--- a/src/frontend/app/administration/entity/entities-administration.component.html
+++ b/src/frontend/app/administration/entity/entities-administration.component.html
@@ -8,6 +8,12 @@
                     {{lang.add}}
                 </p>
             </a>
+            <a mat-list-item (click)="openExportModal()">
+                <mat-icon color="primary" mat-list-icon class="fas fa-file-export"></mat-icon>
+                <p mat-line>
+                    {{lang.toExport}}
+                </p>
+            </a>
         </mat-nav-list>
         <mat-divider></mat-divider>
         <mat-nav-list>
@@ -381,4 +387,4 @@
             </mat-tab-group>
         </mat-nav-list>
     </mat-sidenav>
-</mat-sidenav-container>
\ No newline at end of file
+</mat-sidenav-container>
diff --git a/src/frontend/app/administration/entity/entities-administration.component.ts b/src/frontend/app/administration/entity/entities-administration.component.ts
index c4d4ed4fce0..2a7d590a7bd 100755
--- a/src/frontend/app/administration/entity/entities-administration.component.ts
+++ b/src/frontend/app/administration/entity/entities-administration.component.ts
@@ -17,6 +17,7 @@ import { ConfirmComponent } from '../../../plugins/modal/confirm.component';
 import { VisaWorkflowComponent } from '../../visa/visa-workflow.component';
 import { AvisWorkflowComponent } from '../../avis/avis-workflow.component';
 import { of } from 'rxjs/internal/observable/of';
+import {EntitiesExportComponent} from './export/entities-export.component';
 
 declare var $: any;
 @Component({
@@ -776,6 +777,11 @@ export class EntitiesAdministrationComponent implements OnInit {
                 this.notify.handleErrors(err);
             });
     }
+
+    openExportModal() {
+        this.dialog.open(EntitiesExportComponent, { panelClass: 'maarch-modal', width: '800px', autoFocus: false });
+
+    }
 }
 @Component({
     templateUrl: 'entities-administration-redirect-modal.component.html',
diff --git a/src/frontend/app/administration/entity/export/entities-export.component.html b/src/frontend/app/administration/entity/export/entities-export.component.html
new file mode 100644
index 00000000000..4db1b6b2a95
--- /dev/null
+++ b/src/frontend/app/administration/entity/export/entities-export.component.html
@@ -0,0 +1,37 @@
+<div class="mat-dialog-content-container">
+    <h1 mat-dialog-title>{{'lang.exportDatas' | translate}}</h1>
+    <div mat-dialog-content>
+        <div *ngIf="loadingExport" class="loader">
+            <mat-spinner></mat-spinner>
+        </div>
+        <div class="row">
+            <div [class.col-md-12]="exportModel.format != 'csv'" [class.col-md-3]="exportModel.format == 'csv'">
+                <mat-form-field appearance="outline">
+                    <mat-label>{{'lang.format' | translate}}</mat-label>
+                    <mat-select placeholder="{{lang.format}}" [(ngModel)]="exportModel.format">
+                        <mat-option *ngFor="let format of formats" [value]="format">
+                            {{format}}
+                        </mat-option>
+                    </mat-select>
+                </mat-form-field>
+            </div>
+            <div class="col-md-9" *ngIf="exportModel.format == 'csv'">
+                <mat-form-field appearance="outline">
+                    <mat-label>{{'lang.delimiter' | translate}}</mat-label>
+                    <mat-select placeholder="{{'lang.delimiter' | translate}}" [(ngModel)]="exportModel.delimiter"
+                        [disabled]="exportModel.format != 'csv'">
+                        <mat-option *ngFor="let delimiter of delimiters" [value]="delimiter">
+                            {{delimiter}}
+                        </mat-option>
+                    </mat-select>
+                </mat-form-field>
+            </div>
+        </div>
+    </div>
+    <span class="divider-modal"></span>
+    <div mat-dialog-actions class="actions">
+        <button mat-raised-button mat-button color="primary"
+            (click)="exportData();">{{'lang.toExport' | translate}}</button>
+        <button mat-raised-button mat-button [mat-dialog-close]="">{{'lang.cancel' | translate}}</button>
+    </div>
+</div>
diff --git a/src/frontend/app/administration/entity/export/entities-export.component.scss b/src/frontend/app/administration/entity/export/entities-export.component.scss
new file mode 100644
index 00000000000..a3b7b6763a4
--- /dev/null
+++ b/src/frontend/app/administration/entity/export/entities-export.component.scss
@@ -0,0 +1,26 @@
+::ng-deep.mat-dialog-container {
+  position: relative;
+}
+
+.mat-dialog-content {
+  min-height: 150px;
+  padding-bottom: 10px;
+  overflow-x: hidden;
+}
+
+.actions {
+  justify-content: center;
+}
+
+.loader {
+  position: absolute;
+  display: flex;
+  width: 100%;
+  left: 0px;
+  top: 0px;
+  height: 100%;
+  background: #fff9;
+  z-index: 1;
+  align-items: center;
+  justify-content: center;
+}
\ No newline at end of file
diff --git a/src/frontend/app/administration/entity/export/entities-export.component.ts b/src/frontend/app/administration/entity/export/entities-export.component.ts
new file mode 100644
index 00000000000..bb4f328f77d
--- /dev/null
+++ b/src/frontend/app/administration/entity/export/entities-export.component.ts
@@ -0,0 +1,92 @@
+import { Component, OnInit, ViewChild, Inject } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { LANG } from '../../../translate.component';
+import { NotificationService } from '../../../../service/notification/notification.service';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { catchError, tap, finalize } from 'rxjs/operators';
+import { of } from 'rxjs/internal/observable/of';
+import { LocalStorageService } from '../../../../service/local-storage.service';
+import { HeaderService } from '../../../../service/header.service';
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+    templateUrl: 'entities-export.component.html',
+    styleUrls: ['entities-export.component.scss'],
+})
+export class EntitiesExportComponent implements OnInit {
+
+    lang: any = LANG;
+    loading: boolean = false;
+    loadingExport: boolean = false;
+
+    delimiters = [';', ',', 'TAB'];
+    formats = ['csv'];
+
+    exportModel: any = {
+        delimiter: ';',
+        format: 'csv',
+    };
+
+    exportModelList: any;
+
+    @ViewChild('listFilter', { static: true }) private listFilter: any;
+
+    constructor(
+        private translate: TranslateService,
+        public http: HttpClient,
+        private notify: NotificationService,
+        @Inject(MAT_DIALOG_DATA) public data: any,
+        private localStorage: LocalStorageService,
+        private headerService: HeaderService
+    ) { }
+
+    async ngOnInit(): Promise<void> {
+        this.setConfiguration();
+    }
+
+    exportData() {
+        this.localStorage.save(`exportEntities_${this.headerService.user.id}`, JSON.stringify(this.exportModel));
+        this.loadingExport = true;
+        this.http.put('../rest/entities/export', this.exportModel, { responseType: 'blob' }).pipe(
+            tap((data: any) => {
+                if (data.type !== 'text/html') {
+                    const downloadLink = document.createElement('a');
+                    downloadLink.href = window.URL.createObjectURL(data);
+                    let today: any;
+                    let dd: any;
+                    let mm: any;
+                    let yyyy: any;
+
+                    today = new Date();
+                    dd = today.getDate();
+                    mm = today.getMonth() + 1;
+                    yyyy = today.getFullYear();
+
+                    if (dd < 10) {
+                        dd = '0' + dd;
+                    }
+                    if (mm < 10) {
+                        mm = '0' + mm;
+                    }
+                    today = dd + '-' + mm + '-' + yyyy;
+                    downloadLink.setAttribute('download', 'export_entities_maarch_' + today + '.' + this.exportModel.format.toLowerCase());
+                    document.body.appendChild(downloadLink);
+                    downloadLink.click();
+                } else {
+                    alert(this.translate.instant('lang.tooMuchDatas'));
+                }
+            }),
+            finalize(() => this.loadingExport = false),
+            catchError((err: any) => {
+                this.notify.handleSoftErrors(err);
+                return of(false);
+            })
+        ).subscribe();
+    }
+
+    setConfiguration() {
+        if (this.localStorage.get(`exportUsersFields_${this.headerService.user.id}`) !== null) {
+            this.exportModel.delimiter = JSON.parse(this.localStorage.get(`exportEntitiesFields_${this.headerService.user.id}`)).delimiter;
+        }
+    }
+}
-- 
GitLab