From 62fa1222285f23fa83613896f0bca39389c5ba7c Mon Sep 17 00:00:00 2001 From: Alex ORLUC <alex.orluc@maarch.org> Date: Fri, 10 Jan 2020 23:36:58 +0100 Subject: [PATCH] FEAT #12765 TIME 1:30 front opinion workflow --- .../controllers/ListTemplateController.php | 4 +- src/frontend/app/app.module.ts | 7 +- .../add-avis-model-modal.component.html | 11 + .../add-avis-model-modal.component.scss | 13 + .../add-avis-model-modal.component.ts | 53 +++ .../app/avis/avis-workflow.component.html | 98 +++-- .../app/avis/avis-workflow.component.scss | 106 +++++- .../app/avis/avis-workflow.component.ts | 340 +++++++++++++++--- .../app/process/process.component.html | 18 +- src/frontend/app/process/process.component.ts | 18 +- .../app/visa/visa-workflow.component.ts | 5 - src/frontend/lang/lang-en.ts | 6 +- src/frontend/lang/lang-fr.ts | 6 +- src/frontend/lang/lang-nl.ts | 4 + 14 files changed, 582 insertions(+), 107 deletions(-) create mode 100644 src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.html create mode 100644 src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.scss create mode 100644 src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.ts diff --git a/src/app/entity/controllers/ListTemplateController.php b/src/app/entity/controllers/ListTemplateController.php index 7a3290550ed..729021ce876 100755 --- a/src/app/entity/controllers/ListTemplateController.php +++ b/src/app/entity/controllers/ListTemplateController.php @@ -124,7 +124,7 @@ class ListTemplateController $check = $check && Validator::arrayType()->notEmpty()->validate($body['items']); $check = $check && (Validator::stringType()->notEmpty()->validate($body['title']) || Validator::stringType()->notEmpty()->validate($body['description'])); if (!$check) { - return $response->withStatus(400)->withJson(['errors' => 'Bad Request']); + return $response->withStatus(400)->withJson(['errors' => 'Bad allowed types']); } if (!empty($body['entityId'])) { @@ -603,7 +603,7 @@ class ListTemplateController $circuit = $queryParams['circuit'] == 'opinion' ? 'opinionCircuit' : 'visaCircuit'; $resource = ResModel::getById(['resId' => $args['resId'], 'select' => ['destination']]); - $where = ['type = ?', 'owner is null or owner = ?']; + $where = ['type = ?', '(owner is null or owner = ?)']; $data = [$circuit, $GLOBALS['id']]; if (!empty($resource['destination'])) { $entity = EntityModel::getByEntityId(['entityId' => $resource['destination'], 'select' => ['id']]); diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 44eefa1a128..a8bd7c93cae 100755 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -87,6 +87,7 @@ import { ContactsListModalComponent } from './contact/list/modal/contacts-list-m import { ContactModalComponent } from './administration/contact/modal/contact-modal.component'; import { VisaWorkflowModalComponent } from './visa/modal/visa-workflow-modal.component'; import { AddVisaModelModalComponent } from './visa/addVisaModel/add-visa-model-modal.component'; +import { AddAvisModelModalComponent } from './avis/addAvisModel/add-avis-model-modal.component'; @NgModule({ @@ -164,7 +165,8 @@ import { AddVisaModelModalComponent } from './visa/addVisaModel/add-visa-model-m FollowedDocumentListComponent, FollowedActionListComponent, VisaWorkflowModalComponent, - AddVisaModelModalComponent + AddVisaModelModalComponent, + AddAvisModelModalComponent ], entryComponents: [ ConfirmModalComponent, @@ -195,7 +197,8 @@ import { AddVisaModelModalComponent } from './visa/addVisaModel/add-visa-model-m ContactsListModalComponent, ContactModalComponent, VisaWorkflowModalComponent, - AddVisaModelModalComponent + AddVisaModelModalComponent, + AddAvisModelModalComponent ], providers: [ FiltersListService, FoldersService, ActionsService, PrivilegeService ], bootstrap: [ AppComponent ] diff --git a/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.html b/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.html new file mode 100644 index 00000000000..30d14d0ffb6 --- /dev/null +++ b/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.html @@ -0,0 +1,11 @@ +<h1 mat-dialog-title>Ajouter un modèle</h1> +<mat-dialog-content class="modal-container"> + <mat-form-field appearance="outline"> + <input type="text" matInput [(ngModel)]="template.title" placeholder="Nom du modèle"> + </mat-form-field> +</mat-dialog-content> +<div mat-dialog-actions class="actions"> + <button mat-raised-button mat-button color="primary" [disabled]="loading" + (click)="onSubmit()">{{lang.validate}}</button> + <button mat-raised-button mat-button [disabled]="loading" [mat-dialog-close]="">{{lang.cancel}}</button> +</div> \ No newline at end of file diff --git a/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.scss b/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.scss new file mode 100644 index 00000000000..7a15a35b125 --- /dev/null +++ b/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.scss @@ -0,0 +1,13 @@ +@import '../../../css/vars.scss'; + +.mat-dialog-title { + padding: 10px; +} + +.modal-container{ + height: auto; +} + +.modal-body{ + min-height: auto; +} diff --git a/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.ts b/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.ts new file mode 100644 index 00000000000..6a38ba1da8b --- /dev/null +++ b/src/frontend/app/avis/addAvisModel/add-avis-model-modal.component.ts @@ -0,0 +1,53 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { LANG } from '../../translate.component'; +import { HttpClient } from '@angular/common/http'; +import { tap, catchError } from 'rxjs/operators'; +import { NotificationService } from '../../notification.service'; +import { of } from 'rxjs'; + +@Component({ + templateUrl: 'add-avis-model-modal.component.html', + styleUrls: ['add-avis-model-modal.component.scss'], +}) +export class AddAvisModelModalComponent { + lang: any = LANG; + + template: any = { + id: 0, + type: 'opinionCircuit', + title: '', + items : [] + } + + constructor( + public http: HttpClient, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef<AddAvisModelModalComponent>, + private notify: NotificationService) { } + + ngOnInit(): void { + this.template.items = this.data.avisWorkflow.map((item: any) => { + return { + id: item.item_id, + type: 'user', + mode: 'avis' + } + }); + } + + onSubmit() { + this.http.post(`../../rest/listTemplates`, this.template).pipe( + tap((data: any) => { + this.template.id = data.id; + this.notify.success(this.lang.modelSaved); + this.dialogRef.close(this.template); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } + +} diff --git a/src/frontend/app/avis/avis-workflow.component.html b/src/frontend/app/avis/avis-workflow.component.html index 8738a66543a..350f7885c7a 100644 --- a/src/frontend/app/avis/avis-workflow.component.html +++ b/src/frontend/app/avis/avis-workflow.component.html @@ -1,30 +1,86 @@ + <mat-list *ngIf="!loading"> - <mat-form-field *ngIf="adminMode" appearance="outline" floatLabel="never" [style.fontSize.px]="10"> - <input class="metaSearch" type="text" matInput placeholder="{{lang.addPerson}}"> + <mat-form-field appearance="outline" *ngIf="adminMode && !linkedToMaarchParapheur"> + <input type="text" matInput placeholder="Ajouter des personnes" id="searchAvisSignUserInput" + [formControl]="searchAvisSignUser" [matAutocomplete]="autoGroup"> + <mat-autocomplete #autoGroup="matAutocomplete" (optionSelected)="addItemToWorkflow($event.option.value)" (opened)="initFilterAvisModelList()"> + <mat-option disabled *ngIf="avisModelListNotLoaded"> + <div style="display: flex;justify-content: center;"> + <mat-spinner diameter="35"></mat-spinner> + </div> + </mat-option> + <mat-optgroup [label]="lang.publicModel" *ngIf="(filteredPublicModels | async)?.length > 0" + class="avisSignList"> + <mat-option *ngFor="let model of filteredPublicModels | async | sortBy : 'label'" [value]="model"> + {{model.label}} + </mat-option> + </mat-optgroup> + <mat-optgroup [label]="lang.privateModel" *ngIf="(filteredPrivateModels | async)?.length > 0" + class="avisSignList"> + <mat-option *ngFor="let model of filteredPrivateModels | async | sortBy : 'label'" [value]="model"> + <div style="display: flex;align-items: center;"> + <div style="flex:1"> + {{model.label}} + </div> + <button mat-icon-button color="warn" + (click)="$event.stopPropagation();deletePrivateModel(model)"> + <mat-icon class="fa fa-trash" style="margin: 0px;"></mat-icon> + </button> + </div> + </mat-option> + </mat-optgroup> + <mat-optgroup [label]="lang.user | titlecase" *ngIf="(filteredSignAvisUsers | async)?.length > 0" + class="avisSignList"> + <mat-option *ngFor="let user of filteredSignAvisUsers | async | sortBy : 'label'" [value]="user"> + {{user.label}} <small>({{user.entity}})</small> + </mat-option> + </mat-optgroup> + </mat-autocomplete> + <button mat-icon-button matSuffix *ngIf="avisWorkflow.items.length > 0" color="primary" + (click)="$event.stopPropagation();openPromptSaveModel()"> + <mat-icon class="fa fa-plus"></mat-icon> + </button> </mat-form-field> <div cdkDropList #dataAvailableList="cdkDropList" [cdkDropListData]="avisWorkflow.items" class="cdk-list" (cdkDropListDropped)="drop($event)" [cdkDropListDisabled]="!adminMode"> - <div *ngIf="avisWorkflow.items.length === 0" style="opacity: 0.5;text-align: center;font-size: 10px;padding: 10px;"> + <div class="emptyContent" *ngIf="avisWorkflow.items.length === 0"> {{lang.noPerson}} </div> - <mat-list-item *ngFor="let diffusion of avisWorkflow.items;let i=index" cdkDrag class="columns" - [cdkDragDisabled]="!adminMode" [class.notDraggable]="adminMode" [class.notEditable]="!adminMode" - [ngStyle]="{'background': diffusion.process_date != null ? 'rgba(0, 128, 0, 0.11)' : ''}"> - <mat-icon mat-list-icon class="fa fa-user fa-2x" color="primary"></mat-icon> - <mat-icon mat-list-icon class="fa fa-hourglass fa-2x" *ngIf="diffusion.process_date == null" style="opacity:0.5;"></mat-icon> - <mat-icon mat-list-icon class="fa fa-check fa-2x" *ngIf="diffusion.process_date != null" style="opacity:0.5;" - color="accent"></mat-icon> - <h4 mat-line style="display: flex;"> - <span style="flex: 1;">{{diffusion.item_firstname}} {{diffusion.item_lastname}}</span> - </h4> - <p mat-line style="display: flex;"> - <span style="opacity:0.5;flex: 1;">{{diffusion.item_entity}}</span> - <span *ngIf="diffusion.process_date != null" title='{{diffusion.process_date | date : lang.onRange + " dd/MM/y " + lang.atRange +" HH:mm"}}' - style="flex: 1;text-align: right;font-size: 90%;" color="accent">{{diffusion.process_date - | timeAgo}}</span> - </p> - <button mat-icon-button *ngIf="adminMode" (click)="deleteItem(i)"> + <mat-list-item *ngFor="let diffusion of avisWorkflow.items;let i=index" cdkDrag class="columns workflow" + [cdkDragDisabled]="!adminMode || !functions.empty(diffusion.process_date)" + [class.notDraggable]="!adminMode || !functions.empty(diffusion.process_date)" + [class.notEditable]="!adminMode" [class.processed]="diffusion.process_date != null"> + <mat-icon + [ngClass]="{'fa fa-user fa-2x': functions.empty(diffusion.picture),'avatar': !functions.empty(diffusion.picture)}" + mat-list-icon color="primary" + [style.background-image]="!functions.empty(diffusion.picture) ? 'url('+diffusion.picture+')' : ''"> + </mat-icon> + <ng-container *ngIf="!adminMode || diffusion.process_date != null"> + <mat-icon mat-list-icon class="fa-2x far" + [class.fa-hourglass]="diffusion.process_date == null" [class.fa-thumbs-up]="diffusion.process_date != null" [class.valid]="diffusion.process_date != null" + style="opacity:0.5;"></mat-icon> + </ng-container> + <div mat-line class="workflowLine"> + <div class="workflowLineContainer"> + <div class="workflowLineLabel"> + {{diffusion.labelToDisplay}} + </div> + <div class="workflowLineSubLabel"> + {{diffusion.item_entity}} + </div> + <div *ngIf="diffusion.process_date != null" class="workflowLineProcessDate" + title='{{diffusion.process_date | fullDate}}' + color="accent">{{lang.avisSent}} {{diffusion.process_date + | timeAgo : 'full'}}</div> + </div> + </div> + <button mat-icon-button *ngIf="adminMode && functions.empty(diffusion.process_date)" + (click)="deleteItem(i)"> <mat-icon class="fa fa-times" color="warn"></mat-icon> </button> </mat-list-item> - </div> \ No newline at end of file + </div> +</mat-list> +<div *ngIf="loading" style="display:flex;padding: 10px;"> + <mat-spinner style="margin:auto;"></mat-spinner> +</div> \ No newline at end of file diff --git a/src/frontend/app/avis/avis-workflow.component.scss b/src/frontend/app/avis/avis-workflow.component.scss index e228e789956..5c622317589 100644 --- a/src/frontend/app/avis/avis-workflow.component.scss +++ b/src/frontend/app/avis/avis-workflow.component.scss @@ -1,12 +1,30 @@ +@import '../../css/vars.scss'; + +.mat-form-field-appearance-outline { + font-size: 11px; +} + +.avisSignList { + ::ng-deep.mat-optgroup-label { + color: $primary; + position: sticky; + top: 0px; + background: white !important; + z-index: 1; + } +} + .cdk-drag-preview { box-sizing: border-box; border-radius: 4px; box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); - background: white; + background: white !important; padding: 10px; - .mat-icon { + + .mat-icon, + .mat-icon-button { display: none; } } @@ -37,4 +55,88 @@ .notEditable { cursor: initial; +} + +.currentContextButton { + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; + width: 150px; + text-align: left; +} + +.currentRoleButton { + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; + width: 120px; + text-align: center; +} + +.emptyContent { + opacity: 0.5; + text-align: center; + font-size: 10px; + padding: 10px; +} + +.processed { + background: rgba(0, 128, 0, 0.11) !important; +} + +.workflow { + height: 55px; + margin-bottom: 10px; + background: rgba(216,216,216,0.1); + border-radius: 10px; + font-size: 13px; +} + +.workflowLine { + display: flex !important; + align-items: center; + + &Container { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + } + + &Label { + text-overflow: ellipsis; + overflow: hidden; + } + + &SubLabel { + font-size: 80%; + opacity: 0.5; + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + } + + &ProcessDate { + flex: 1; + text-align: left; + font-size: 80%; + } + + .mat-raised-button[disabled] { + background: none; + color: $primary !important; + opacity: 1; + } +} + +.avatar { + border: solid 3px #F99830; + height: 45px !important; + width: 45px !important; + background-size: cover; + background-repeat: no-repeat; + background-position: center; +} + +.valid { + color: $accent; } \ No newline at end of file diff --git a/src/frontend/app/avis/avis-workflow.component.ts b/src/frontend/app/avis/avis-workflow.component.ts index 043b1566a1c..5a8ca69f597 100644 --- a/src/frontend/app/avis/avis-workflow.component.ts +++ b/src/frontend/app/avis/avis-workflow.component.ts @@ -1,31 +1,63 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, ElementRef, ViewChild } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { LANG } from '../translate.component'; import { NotificationService } from '../notification.service'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { FunctionsService } from '../../service/functions.service'; +import { tap, exhaustMap, map, startWith, catchError, finalize, filter, debounceTime, switchMap } from 'rxjs/operators'; +import { FormControl } from '@angular/forms'; +import { LatinisePipe } from 'ngx-pipes'; +import { Observable, of } from 'rxjs'; +import { MatDialog } from '@angular/material'; +import { AddAvisModelModalComponent } from './addAvisModel/add-avis-model-modal.component'; +import { ConfirmComponent } from '../../plugins/modal/confirm.component'; + +declare function $j(selector: any): any; @Component({ selector: 'app-avis-workflow', templateUrl: 'avis-workflow.component.html', - styleUrls: ['avis-workflow.component.scss'], - providers: [NotificationService] + styleUrls: ['avis-workflow.component.scss'] }) export class AvisWorkflowComponent implements OnInit { lang: any = LANG; avisWorkflow: any = { - items : [] + roles: ['sign', 'avis'], + items: [] + }; + avisWorkflowClone: any = null; + avisTemplates: any = { + private: [], + public: [] }; - loading: boolean = true; + + signAvisUsers: any = []; + filteredSignAvisUsers: Observable<string[]>; + filteredPublicModels: Observable<string[]>; + filteredPrivateModels: Observable<string[]>; + + loading: boolean = false; + avisModelListNotLoaded: boolean = true; data: any; @Input('injectDatas') injectDatas: any; @Input('adminMode') adminMode: boolean; @Input('resId') resId: number = null; - constructor(public http: HttpClient, private notify: NotificationService) { } + @ViewChild('searchAvisSignUserInput', { static: true }) searchAvisSignUserInput: ElementRef; - ngOnInit(): void { + searchAvisSignUser = new FormControl(); + + constructor( + public http: HttpClient, + private notify: NotificationService, + public functions: FunctionsService, + private latinisePipe: LatinisePipe, + public dialog: MatDialog + ) { } + + ngOnInit(): void { if (this.resId !== null) { this.loadWorkflow(this.resId); } @@ -33,70 +65,158 @@ export class AvisWorkflowComponent implements OnInit { drop(event: CdkDragDrop<string[]>) { if (event.previousContainer === event.container) { - moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + if (this.functions.empty(this.avisWorkflow.items[event.currentIndex].process_date)) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + this.notify.error(`${this.lang.moveAvisUserErr1} <b>${this.avisWorkflow.items[event.previousIndex].labelToDisplay}</b> ${this.lang.moveAvisUserErr2}.`); + } } } - loadListModel(entityId: string) { + loadListModel(entityId: number) { this.loading = true; this.avisWorkflow.items = []; - - // TO DO : ADD ROUTE - /*this.http.get("../../rest/???") + this.http.get(`../../rest/listTemplates/entities/${entityId}?type=opinionCircuit`) .subscribe((data: any) => { - this.loading = false; - });*/ - - this.avisWorkflow.items.push( - { - "listinstance_id": 20, - "sequence": 0, - "item_mode": "avis", - "item_id": "bbain", - "item_type": "user_id", - "item_firstname": "Barbara", - "item_lastname": "BAIN", - "item_entity": "P\u00f4le Jeunesse et Sport", - "viewed": 0, - "process_date": null, - "process_comment": "", - "signatory": false, - "requested_signature": false + if (data.listTemplates[0]) { + this.avisWorkflow.items = data.listTemplates[0].items.map((item: any) => { + return { + ...item, + item_entity: item.descriptionToDisplay, + } + }); + this.loading = false; + } }); + } - this.avisWorkflow.items.push( - { - "listinstance_id": 21, - "sequence": 0, - "item_mode": "avis", - "item_id": "DSG", - "item_type": "entity_id", - "item_entity": "Secr\u00e9tariat G\u00e9n\u00e9ral", - "viewed": 0, - "process_date": null, - "process_comment": null, - "signatory": false, - "requested_signature": false - } - ); + loadAvisSignUsersList() { + return new Promise((resolve, reject) => { + this.http.get(`../../rest/autocomplete/users/circuit`).pipe( + map((data: any) => { + data = data.map((user: any) => { + return { + id: user.id, + title: `${user.idToDisplay} (${user.otherInfo})`, + label: user.idToDisplay, + entity: user.otherInfo, + type: 'user' + } + }); + return data; + }), + tap((data) => { + this.signAvisUsers = data; + this.filteredSignAvisUsers = this.searchAvisSignUser.valueChanges + .pipe( + startWith(''), + map(value => this._filter(value)) + ); + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + }); + } + + loadAvisModelListByResource() { + return new Promise((resolve, reject) => { + this.http.get(`../../rest/resources/${this.resId}/availableCircuits?circuit=opinion`).pipe( + tap((data: any) => { + this.avisTemplates.public = data.circuits.filter((item: any) => !item.private).map((item: any) => { + return { + id: item.id, + title: item.title, + label: item.title, + type: 'entity' + } + }); + + this.avisTemplates.private = data.circuits.filter((item: any) => item.private).map((item: any) => { + return { + id: item.id, + title: item.title, + label: item.title, + type: 'entity' + } + }); + this.filteredPublicModels = this.searchAvisSignUser.valueChanges + .pipe( + startWith(''), + map(value => this._filterPublicModel(value)) + ); + this.filteredPrivateModels = this.searchAvisSignUser.valueChanges + .pipe( + startWith(''), + map(value => this._filterPrivateModel(value)) + ); + resolve(true); + }), + ).subscribe(); + }); + } + + async initFilterAvisModelList() { + if (this.avisModelListNotLoaded) { + await this.loadAvisSignUsersList(); + + await this.loadAvisModelListByResource(); + + this.searchAvisSignUser.reset(); + + this.avisModelListNotLoaded = false; + } + } + + private _filter(value: string): string[] { + if (typeof value === 'string') { + const filterValue = this.latinisePipe.transform(value.toLowerCase()); + return this.signAvisUsers.filter((option: any) => this.latinisePipe.transform(option['title'].toLowerCase()).includes(filterValue)); + } else { + return this.signAvisUsers; + } + } + + private _filterPrivateModel(value: string): string[] { + if (typeof value === 'string') { + const filterValue = this.latinisePipe.transform(value.toLowerCase()); + return this.avisTemplates.private.filter((option: any) => this.latinisePipe.transform(option['title'].toLowerCase()).includes(filterValue)); + } else { + return this.avisTemplates.private; + } + } - this.loading = false; + private _filterPublicModel(value: string): string[] { + if (typeof value === 'string') { + const filterValue = this.latinisePipe.transform(value.toLowerCase()); + return this.avisTemplates.public.filter((option: any) => this.latinisePipe.transform(option['title'].toLowerCase()).includes(filterValue)); + } else { + return this.avisTemplates.public; + } } loadWorkflow(resId: number) { this.loading = true; this.avisWorkflow.items = []; this.http.get("../../rest/resources/" + resId + "/opinionCircuit") - .subscribe((data: any) => { - data.forEach((element:any) => { - this.avisWorkflow.items.push(element); + .subscribe((data: any) => { + data.forEach((element: any) => { + this.avisWorkflow.items.push( + { + ...element, + difflist_type: 'AVIS_CIRCUIT' + }); + }); + this.avisWorkflowClone = JSON.parse(JSON.stringify(this.avisWorkflow.items)) + this.loading = false; + }, (err: any) => { + this.notify.handleErrors(err); }); - this.loading = false; - }, (err: any) => { - this.notify.handleErrors(err); - }); } deleteItem(index: number) { @@ -106,4 +226,118 @@ export class AvisWorkflowComponent implements OnInit { getAvisCount() { return this.avisWorkflow.items.length; } + + changeRole(i: number) { + this.avisWorkflow.items[i].requested_signature = !this.avisWorkflow.items[i].requested_signature; + } + + getWorkflow() { + return this.avisWorkflow.items; + } + + saveAvisWorkflow() { + this.http.put(`../../rest/listinstances`, [{ resId: this.resId, listInstances: this.avisWorkflow.items }]).pipe( + tap((data: any) => { + this.avisWorkflowClone = JSON.parse(JSON.stringify(this.avisWorkflow.items)); + this.notify.success(this.lang.avisWorkflowUpdated); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } + + addItemToWorkflow(item: any) { + if (item.type === 'user') { + this.avisWorkflow.items.push({ + item_id: item.id, + item_type: 'user', + item_entity: item.entity, + labelToDisplay: item.label, + externalId: !this.functions.empty(item.externalId) ? item.externalId : null, + difflist_type: 'AVIS_CIRCUIT', + signatory: false, + requested_signature: false + }); + this.searchAvisSignUser.reset(); + } else if (item.type === 'entity') { + this.http.get(`../../rest/listTemplates/${item.id}`).pipe( + tap((data: any) => { + this.avisWorkflow.items = this.avisWorkflow.items.concat( + data.listTemplate.items.map((itemTemplate: any) => { + return { + item_id: itemTemplate.item_id, + item_type: 'user', + labelToDisplay: itemTemplate.idToDisplay, + item_entity: itemTemplate.descriptionToDisplay, + difflist_type: 'AVIS_CIRCUIT', + signatory: false, + requested_signature: false + } + }) + ); + this.searchAvisSignUser.reset(); + }) + ).subscribe(); + } + } + + openPromptSaveModel() { + const dialogRef = this.dialog.open(AddAvisModelModalComponent, { data: { avisWorkflow: this.avisWorkflow.items } }); + + dialogRef.afterClosed().pipe( + filter((data: string) => !this.functions.empty(data)), + + tap((data: any) => { + this.avisTemplates.private.push({ + id: data.id, + title: data.title, + label: data.title, + type: 'entity' + }); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } + + deletePrivateModel(model: any) { + const dialogRef = this.dialog.open(ConfirmComponent, { autoFocus: false, disableClose: true, data: { title: this.lang.delete, msg: this.lang.confirmAction } }); + + dialogRef.afterClosed().pipe( + filter((data: string) => data === 'ok'), + exhaustMap(() => this.http.delete(`../../rest/listTemplates/${model.id}`)), + tap(() => { + this.avisTemplates.private = this.avisTemplates.private.filter((template: any) => template.id !== model.id); + this.searchAvisSignUser.reset(); + this.notify.success(this.lang.modelDeleted); + }), + catchError((err: any) => { + this.notify.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + + getMaarchParapheurUserAvatar(externalId: string, key: number) { + if (!this.functions.empty(externalId)) { + this.http.get("../../rest/maarchParapheur/user/" + externalId + "/picture") + .subscribe((data: any) => { + this.avisWorkflow.items[key].picture = data.picture; + }, (err: any) => { + this.notify.handleErrors(err); + }); + } + } + + isModified() { + if (this.loading || JSON.stringify(this.avisWorkflow.items) === JSON.stringify(this.avisWorkflowClone)) { + return false; + } else { + return true; + } + } } diff --git a/src/frontend/app/process/process.component.html b/src/frontend/app/process/process.component.html index e522ca380f2..b5ec5b245d3 100644 --- a/src/frontend/app/process/process.component.html +++ b/src/frontend/app/process/process.component.html @@ -79,7 +79,7 @@ <app-visa-workflow *ngIf="currentTool === 'visa' && !loading" #appVisaWorkflow [resId]="currentResourceInformations.resId" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_visa_workflow')"></app-visa-workflow> <app-avis-workflow *ngIf="currentTool === 'avis' && !loading" #appAvisWorkflow - [resId]="currentResourceInformations.resId"></app-avis-workflow> + [resId]="currentResourceInformations.resId" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_avis_workflow')"></app-avis-workflow> <app-attachments-list *ngIf="currentTool === 'attachments' && !loading" #appAttachmentsList [resId]="currentResourceInformations.resId" [target]="'process'" (reloadBadgeAttachments)="refreshBadge($event,'attachments')"> @@ -89,19 +89,9 @@ [mode]="'process'" [canEdit]="canEditData" [hideDiffusionList]="true" (loadingFormEndEvent)="triggerProcessAction()"></app-indexing-form> <div style="position: sticky;bottom: 0px;text-align:right;"> - <button mat-fab - *ngIf="indexingForm !== undefined && indexingForm.isResourceModified() && currentTool === 'info'" - (click)="confirmModification()" color="accent" [title]="lang.saveModifications"> - <mat-icon style="height:auto;font-size:20px;" class="fas fa-check"></mat-icon> - </button> - <button mat-fab [title]="lang.saveModifications" - *ngIf="appDiffusionsList !== undefined && appDiffusionsList.isModified() && currentTool === 'diffusionList'" - (click)="saveListinstance()" color="accent"> - <mat-icon style="height:auto;font-size:20px;" class="fas fa-check"></mat-icon> - </button> <button mat-fab [title]="lang.saveModifications" - *ngIf="appVisaWorkflow !== undefined && appVisaWorkflow.isModified() && currentTool === 'visa'" - (click)="saveVisaWorkflow()" color="accent"> + *ngIf="isToolModified()" + (click)="saveTool()" color="accent"> <mat-icon style="height:auto;font-size:20px;" class="fas fa-check"></mat-icon> </button> </div> @@ -258,7 +248,7 @@ [resId]="currentResourceInformations.resId" > </app-visa-workflow> <app-avis-workflow *ngIf="modal.id === 'avis' && !loading" #appAvisWorkflow - [resId]="currentResourceInformations.resId"> + [resId]="currentResourceInformations.resId" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_avis_workflow')"> </app-avis-workflow> <app-attachments-list *ngIf="modal.id === 'attachments' && !loading" #appAttachmentsList [resId]="currentResourceInformations.resId" (reloadBadgeAttachments)="refreshBadge($event,'attachments')"> diff --git a/src/frontend/app/process/process.component.ts b/src/frontend/app/process/process.component.ts index 64319f6a440..01474838c9e 100755 --- a/src/frontend/app/process/process.component.ts +++ b/src/frontend/app/process/process.component.ts @@ -23,6 +23,7 @@ import { DiffusionsListComponent } from '../diffusions/diffusions-list.component import { ContactService } from '../../service/contact.service'; import { VisaWorkflowComponent } from '../visa/visa-workflow.component'; import { PrivilegeService } from '../../service/privileges.service'; +import { AvisWorkflowComponent } from '../avis/avis-workflow.component'; @@ -140,6 +141,7 @@ export class ProcessComponent implements OnInit { @ViewChild('indexingForm', { static: false }) indexingForm: IndexingFormComponent; @ViewChild('appDiffusionsList', { static: false }) appDiffusionsList: DiffusionsListComponent; @ViewChild('appVisaWorkflow', { static: false }) appVisaWorkflow: VisaWorkflowComponent; + @ViewChild('appAvisWorkflow', { static: false }) appAvisWorkflow: AvisWorkflowComponent; senderLightInfo: any = { 'displayName': null, 'fillingRate': null }; hasContact: boolean = false; @@ -494,11 +496,13 @@ export class ProcessComponent implements OnInit { } isToolModified() { - if (this.currentTool === 'info' && this.indexingForm.isResourceModified()) { + if (this.currentTool === 'info' && this.indexingForm !== undefined && this.indexingForm.isResourceModified()) { return true; - } else if (this.currentTool === 'diffusionList' && this.appDiffusionsList.isModified()) { + } else if (this.currentTool === 'diffusionList' && this.appDiffusionsList !== undefined && this.appDiffusionsList.isModified()) { return true; - } else if (this.currentTool === 'visa' && this.appVisaWorkflow.isModified()) { + } else if (this.currentTool === 'visa' && this.appVisaWorkflow !== undefined && this.appVisaWorkflow.isModified()) { + return true; + } else if (this.currentTool === 'avis' && this.appAvisWorkflow !== undefined && this.appAvisWorkflow.isModified()) { return true; } else { return false; @@ -514,12 +518,14 @@ export class ProcessComponent implements OnInit { } saveTool() { - if (this.currentTool === 'info') { + if (this.currentTool === 'info' && this.indexingForm !== undefined) { this.indexingForm.saveData(this.currentUserId, this.currentGroupId, this.currentBasketId); - } else if (this.currentTool === 'diffusionList') { + } else if (this.currentTool === 'diffusionList' && this.appDiffusionsList !== undefined) { this.appDiffusionsList.saveListinstance(); - } else if (this.currentTool === 'visa') { + } else if (this.currentTool === 'visa' && this.appVisaWorkflow !== undefined) { this.appVisaWorkflow.saveVisaWorkflow(); + } else if (this.currentTool === 'avis' && this.appAvisWorkflow !== undefined) { + this.appAvisWorkflow.saveAvisWorkflow(); } } diff --git a/src/frontend/app/visa/visa-workflow.component.ts b/src/frontend/app/visa/visa-workflow.component.ts index df8b0631ad4..537bc4630e4 100644 --- a/src/frontend/app/visa/visa-workflow.component.ts +++ b/src/frontend/app/visa/visa-workflow.component.ts @@ -397,11 +397,6 @@ export class VisaWorkflowComponent implements OnInit { ).subscribe(); } - addItem(userRest: any) { - - - } - getMaarchParapheurUserAvatar(externalId: string, key: number) { if (!this.functions.empty(externalId)) { this.http.get("../../rest/maarchParapheur/user/" + externalId + "/picture") diff --git a/src/frontend/lang/lang-en.ts b/src/frontend/lang/lang-en.ts index c2801276fac..9dfa6774736 100755 --- a/src/frontend/lang/lang-en.ts +++ b/src/frontend/lang/lang-en.ts @@ -1392,5 +1392,9 @@ export const LANG_EN = { "publicModel" : "Public model", "privateModel" : "Private model", "moveVisaUserErr1" : "You cannot move", - "moveVisaUserErr2" : "with users who have already approved / signed", + "moveVisaUserErr2" : "with users who have already approved / signed", + "moveAvisUserErr1" : "You cannot move", + "moveAvisUserErr2" : "with users who have already given an opinion", + "avisWorkflowUpdated" : "Opinion workflow updated", + "avisSent" : "Opinion given", }; diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts index 00b1ee8b950..21270cfcd60 100755 --- a/src/frontend/lang/lang-fr.ts +++ b/src/frontend/lang/lang-fr.ts @@ -1420,7 +1420,7 @@ export const LANG_FR = { "closeEditor" : "Fermer l'éditeur", "errorOnlyoffice1" : "Impossible de lancer onlyoffice, vous utilisez une adresse locale", "errorOnlyoffice2" : "Impossible de lancer onlyoffice. Veuillez vérifier la disponibilité du serveur", - "externalVisaWorkflow" : "Circuit de visa Maarch parapheur", + "externalVisaWorkflow" : "Circuit de visa Maarch Parapheur", "IdMaarch2Gec" : "Identifiant MAARCH2GEC", "indexingFile" : "Indexation", "signUserRequired" : "Un signataire mininum est requis", @@ -1433,4 +1433,8 @@ export const LANG_FR = { "privateModel" : "Modèle privé", "moveVisaUserErr1" : "Vous ne pouvez pas déplacer", "moveVisaUserErr2" : "avec des personnes ayant déja visé / signé", + "moveAvisUserErr1" : "Vous ne pouvez pas déplacer", + "moveAvisUserErr2" : "avec des personnes ayant déja donné un avis", + "avisWorkflowUpdated" : "Circuit d'avis modifié", + "avisSent" : "Avis donné", }; diff --git a/src/frontend/lang/lang-nl.ts b/src/frontend/lang/lang-nl.ts index 47bb4296555..4408bf5d486 100755 --- a/src/frontend/lang/lang-nl.ts +++ b/src/frontend/lang/lang-nl.ts @@ -1416,4 +1416,8 @@ export const LANG_NL = { "privateModel" : "Private model", //_TO_TRANSLATE "moveVisaUserErr1" : "You cannot move", //_TO_TRANSLATE "moveVisaUserErr2" : "with users who have already approved / signed", //_TO_TRANSLATE + "moveAvisUserErr1" : "You cannot move", //_TO_TRANSLATE + "moveAvisUserErr2" : "with users who have already given an opinion", //_TO_TRANSLATE + "avisWorkflowUpdated" : "Opinion workflow updated", //_TO_TRANSLATE + "avisSent" : "Opinion given", //_TO_TRANSLATE }; -- GitLab