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