Skip to content
Snippets Groups Projects
Commit 8ddbf8d6 authored by Alex ORLUC's avatar Alex ORLUC
Browse files

FEAT #10633 TIME 0:30 add pre process avis circuit

parent d20b33f1
No related branches found
No related tags found
No related merge requests found
Showing
with 395 additions and 46 deletions
......@@ -29,6 +29,7 @@ import { SendSignatureBookActionComponent } from './visa-send-signature-book-act
import { ContinueVisaCircuitActionComponent } from './visa-continue-circuit-action/continue-visa-circuit-action.component';
import { SendAvisWorkflowComponent } from './avis-workflow-send-action/send-avis-workflow-action.component';
import { ContinueAvisCircuitActionComponent } from './avis-continue-circuit-action/continue-avis-circuit-action.component';
import { SendAvisParallelComponent } from './avis-parallel-send-action/send-avis-parallel-action.component';
@Injectable()
export class ActionsService {
......@@ -753,6 +754,28 @@ export class ActionsService {
).subscribe();
}
sendToParallelOpinion(options: any = null) {
const dialogRef = this.dialog.open(SendAvisParallelComponent, {
autoFocus: false,
disableClose: true,
data: this.setDatasActionToSend()
});
dialogRef.afterClosed().pipe(
tap((data: any) => {
this.unlockResourceAfterActionModal(data);
}),
filter((data: string) => data === 'success'),
tap((result: any) => {
this.endAction(result);
}),
finalize(() => this.loading = false),
catchError((err: any) => {
this.notify.handleErrors(err);
return of(false);
})
).subscribe();
}
continueOpinionCircuitAction(options: any = null) {
const dialogRef = this.dialog.open(ContinueAvisCircuitActionComponent, {
autoFocus: false,
......
......@@ -43,9 +43,8 @@ export class ContinueAvisCircuitActionComponent implements OnInit {
this.resourcesErrors = [];
this.resourcesWarnings = [];
// TO DO : WAIT BACK
return new Promise((resolve, reject) => {
this.http.post('../../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkContinueVisaCircuit', { resources: this.data.resIds })
this.http.post('../../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkContinueOpinionCircuit', { resources: this.data.resIds })
.subscribe((data: any) => {
if (!this.functions.empty(data.resourcesInformations.warning)) {
this.resourcesWarnings = data.resourcesInformations.warning;
......
<h1 mat-dialog-title>{{data.action.label}}</h1>
<div mat-dialog-content>
<div *ngIf="loading" class="loading" style="display:flex;height:100%;">
<mat-spinner style="margin:auto;"></mat-spinner>
</div>
<mat-sidenav-container autosize style="height:100%;">
<mat-sidenav-content style="background: white;padding:10px;">
<div>
{{lang.makeActionOn}}
<b *ngIf="data.resIds.length === 0" color="primary" class="highlight">{{lang.currentIndexingMail}}</b>
<b *ngIf="data.resIds.length == 1" color="primary" class="highlight">{{data.resource.chrono}}</b>
<b *ngIf="data.resIds.length > 1" color="primary" class="highlight">{{data.resIds.length}}
{{lang.elements}}</b> ?
</div>
<div *ngIf="resourcesError.length > 0" class="alert-message alert-message-danger mailList" role="alert">
<p>
{{lang.canNotMakeAction}} :
</p>
<ul>
<li *ngFor="let ressource of resourcesError">
<b>{{ressource.alt_identifier}}</b> : {{lang[ressource.reason]}}
</li>
</ul>
</div>
<mat-form-field (click)="picker.open()" appearance="outline" style="cursor:pointer;margin-top: 10px;">
<mat-label>{{lang.opinionLimitDate}}</mat-label>
<input matInput [(ngModel)]="opinionLimitDate" [matDatepicker]="picker" [placeholder]="lang.chooseDate"
[min]="today" readonly style="cursor:pointer;" required>
<mat-datepicker-toggle matSuffix [for]="picker" *ngIf="!opinionLimitDate">
</mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<button mat-button color="warn" matSuffix mat-icon-button *ngIf="opinionLimitDate"
(click)="$event.stopPropagation();opinionLimitDate = null;" [title]="lang.eraseValue">
<mat-icon color="warn" class="fa fa-calendar-times">
</mat-icon>
</button>
</mat-form-field>
<app-note-editor #noteEditor [title]="lang.addOpinionReason" [resIds]="data.resIds"></app-note-editor>
<app-avis-workflow *ngIf="data.resIds.length == 1 || (!noResourceToProcess && data.resIds.length > 1)"
[adminMode]="true" [mode]="'parallel'" #appAvisWorkflow>
</app-avis-workflow>
</mat-sidenav-content>
</mat-sidenav-container>
</div>
<div mat-dialog-actions class="actions">
<button mat-raised-button mat-button color="primary" [disabled]="loading || !isValidAction()"
(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
@import '../../../css/vars.scss';
.fullHeight {
height: 70vh;
}
.fullWidth {
width: 70vw;
}
.highlight {
font-size: 110%;
}
.loading {
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #ffffffb3;
z-index: 2;
overflow: hidden;
}
.mailList {
ul {
font-size: 12px;
max-height: 100px;
overflow: auto;
padding-left: 25px;
padding-right: 5px;
padding-bottom: 10px;
margin-top: 10px;
}
p {
//font-size: 18px;
margin: 0;
text-decoration: underline;
}
b {
font-size: 120%;
}
}
.formType {
align-items: center;
display: flex;
margin: 10px;
border-radius: 4px;
border: solid 1px #ccc;
position: relative;
padding: 10px;
&-title {
white-space: pre;
overflow: hidden;
max-width: 85%;
text-overflow: ellipsis;
z-index: 1;
font-size: 10px;
font-weight: bold;
background: white;
position: absolute;
top: -7px;
left: 10px;
padding: 0px;
margin: 0px;
color: #135f7f;
}
::ng-deep.mat-form-field-suffix {
color: $secondary;
font-size: 15px;
top: 0;
}
::ng-deep.mat-form-field-wrapper {
padding: 0;
}
}
.priceContent {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
&-label {
text-align: right;
color: $primary;
flex: 1;
justify-content: flex-end;
display: flex;
padding-right: 10px;
}
.mat-form-field {
width: 90px !important;
input {
font-weight: bold;
user-select: none;
}
}
}
.priceInfo {
padding-right: 20px;
font-size: 10px;
opacity: 0.5;
width: 100%;
}
.pjList {
display: flex;
width: 100%;
overflow: auto;
flex-direction: column;
background: #666;
img {
margin: 10px;
}
}
\ No newline at end of file
import { Component, OnInit, Inject, ViewChild } from '@angular/core';
import { LANG } from '../../translate.component';
import { NotificationService } from '../../notification.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http';
import { NoteEditorComponent } from '../../notes/note-editor.component';
import { tap, finalize, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { FunctionsService } from '../../../service/functions.service';
import { AvisWorkflowComponent } from '../../avis/avis-workflow.component';
@Component({
templateUrl: "send-avis-parallel-action.component.html",
styleUrls: ['send-avis-parallel-action.component.scss'],
})
export class SendAvisParallelComponent implements OnInit {
lang: any = LANG;
loading: boolean = false;
resourcesError: any[] = [];
noResourceToProcess: boolean = null;
opinionLimitDate: Date = null;
today: Date = new Date();
availableRoles: any[] = [];
@ViewChild('noteEditor', { static: true }) noteEditor: NoteEditorComponent;
@ViewChild('appAvisWorkflow', { static: false }) appAvisWorkflow: AvisWorkflowComponent;
constructor(
public http: HttpClient,
private notify: NotificationService,
public dialogRef: MatDialogRef<SendAvisParallelComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
public functions: FunctionsService) { }
async ngOnInit(): Promise<void> {
if (this.data.resIds.length > 0) {
this.loading = true;
await this.checkAvisParallel();
this.loading = false;
}
}
checkAvisParallel() {
this.resourcesError = [];
return new Promise((resolve, reject) => {
this.http.post('../../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkOpinionInfo', { resources: this.data.resIds })
.subscribe((data: any) => {
if (!this.functions.empty(data.resourcesInformations.noAttachment)) {
this.resourcesError = data.resourcesInformations.noAttachment;
}
this.noResourceToProcess = this.data.resIds.length === this.resourcesError.length;
resolve(true);
}, (err: any) => {
this.notify.handleSoftErrors(err);
});
});
}
async onSubmit() {
this.loading = true;
if (this.data.resIds.length === 0) {
let res = await this.indexDocument();
if (res) {
res = await this.appAvisWorkflow.saveAvisWorkflow(this.data.resIds);
}
if (res) {
this.executeAction(this.data.resIds);
}
} else {
const realResSelected: number[] = this.data.resIds.filter((resId: any) => this.resourcesError.map(resErr => resErr.res_id).indexOf(resId) === -1);
const res = await this.appAvisWorkflow.saveAvisWorkflow(realResSelected);
if (res) {
this.executeAction(realResSelected);
}
}
this.loading = false;
}
indexDocument() {
return new Promise((resolve, reject) => {
this.http.post('../../rest/resources', this.data.resource).pipe(
tap((data: any) => {
this.data.resIds = [data.resId];
resolve(true);
}),
catchError((err: any) => {
this.notify.handleErrors(err);
resolve(false);
return of(false);
})
).subscribe();
});
}
executeAction(realResSelected: number[]) {
const noteContent: string = `[POUR AVIS] ${this.noteEditor.getNoteContent()}`;
this.http.put(this.data.processActionRoute, { resources: realResSelected, note: noteContent, data: { opinionLimitDate: this.functions.formatDateObjectToFrenchDateString(this.opinionLimitDate, true), opinionCircuit : this.appAvisWorkflow.getWorkflow() } }).pipe(
tap((data: any) => {
if (!data) {
this.dialogRef.close('success');
}
if (data && data.errors != null) {
this.notify.error(data.errors);
}
}),
finalize(() => this.loading = false),
catchError((err: any) => {
this.notify.handleErrors(err);
return of(false);
})
).subscribe();
}
isValidAction() {
if (!this.noResourceToProcess && this.appAvisWorkflow !== undefined && !this.appAvisWorkflow.emptyWorkflow() && !this.appAvisWorkflow.workflowEnd() && !this.functions.empty(this.noteEditor.getNoteContent()) && !this.functions.empty(this.functions.formatDateObjectToFrenchDateString(this.opinionLimitDate))) {
return true;
} else {
return false;
}
}
}
......@@ -23,7 +23,7 @@
</ul>
</div>
<mat-form-field (click)="picker.open()" appearance="outline" style="cursor:pointer;margin-top: 10px;">
<mat-label>Date limite de l'avis</mat-label>
<mat-label>{{lang.opinionLimitDate}}</mat-label>
<input matInput [(ngModel)]="opinionLimitDate" [matDatepicker]="picker" [placeholder]="lang.chooseDate"
[min]="today" readonly style="cursor:pointer;" required>
<mat-datepicker-toggle matSuffix [for]="picker" *ngIf="!opinionLimitDate">
......
import { Component, OnInit, Inject, ViewChild } from '@angular/core';
import { Component, OnInit, Inject, ViewChild, AfterViewInit } from '@angular/core';
import { LANG } from '../../translate.component';
import { NotificationService } from '../../notification.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
......@@ -13,7 +13,7 @@ import { AvisWorkflowComponent } from '../../avis/avis-workflow.component';
templateUrl: "send-avis-workflow-action.component.html",
styleUrls: ['send-avis-workflow-action.component.scss'],
})
export class SendAvisWorkflowComponent implements OnInit {
export class SendAvisWorkflowComponent implements AfterViewInit {
lang: any = LANG;
loading: boolean = false;
......@@ -36,12 +36,7 @@ export class SendAvisWorkflowComponent implements OnInit {
@Inject(MAT_DIALOG_DATA) public data: any,
public functions: FunctionsService) { }
async ngOnInit(): Promise<void> {
if (this.data.resIds.length > 0) {
this.loading = true;
await this.checkAvisWorkflow();
this.loading = false;
}
async ngAfterViewInit(): Promise<void> {
if (this.data.resIds.length === 1) {
await this.appAvisWorkflow.loadWorkflow(this.data.resIds[0]);
if (this.appAvisWorkflow.emptyWorkflow()) {
......@@ -50,24 +45,6 @@ export class SendAvisWorkflowComponent implements OnInit {
}
}
checkAvisWorkflow() {
this.resourcesError = [];
// TO DO : WAIT BACK
return new Promise((resolve, reject) => {
this.http.post('../../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkSignatureBook', { resources: this.data.resIds })
.subscribe((data: any) => {
if (!this.functions.empty(data.resourcesInformations.noAttachment)) {
this.resourcesError = data.resourcesInformations.noAttachment;
}
this.noResourceToProcess = this.data.resIds.length === this.resourcesError.length;
resolve(true);
}, (err: any) => {
this.notify.handleSoftErrors(err);
});
});
}
async onSubmit() {
this.loading = true;
if (this.data.resIds.length === 0) {
......
......@@ -61,6 +61,7 @@ import { SendSignatureBookActionComponent } from './actions/visa-s
import { ContinueVisaCircuitActionComponent } from './actions/visa-continue-circuit-action/continue-visa-circuit-action.component';
import { ContinueAvisCircuitActionComponent } from './actions/avis-continue-circuit-action/continue-avis-circuit-action.component';
import { SendAvisWorkflowComponent } from './actions/avis-workflow-send-action/send-avis-workflow-action.component';
import { SendAvisParallelComponent } from './actions/avis-parallel-send-action/send-avis-parallel-action.component';
import { FiltersListComponent } from './list/filters/filters-list.component';
import { FiltersToolComponent } from './list/filters/filters-tool.component';
......@@ -157,6 +158,7 @@ import { AddAvisModelModalComponent } from './avis/addAvisModel/add-avis-model-m
ContinueVisaCircuitActionComponent,
ContinueAvisCircuitActionComponent,
SendAvisWorkflowComponent,
SendAvisParallelComponent,
ActionsListComponent,
PrintSeparatorComponent,
FolderPinnedComponent,
......@@ -204,6 +206,7 @@ import { AddAvisModelModalComponent } from './avis/addAvisModel/add-avis-model-m
ResetVisaActionComponent,
InterruptVisaActionComponent,
SendAvisWorkflowComponent,
SendAvisParallelComponent,
UpdateDepartureDateActionComponent,
SendExternalSignatoryBookActionComponent,
SendExternalNoteBookActionComponent,
......
<mat-list *ngIf="!loading">
<mat-form-field appearance="outline" *ngIf="adminMode && !linkedToMaarchParapheur">
<input type="text" matInput placeholder="Ajouter des personnes" id="searchAvisUserInput"
[formControl]="searchAvisUser" [matAutocomplete]="autoGroup">
<mat-autocomplete #autoGroup="matAutocomplete" (optionSelected)="addItemToWorkflow($event.option.value)" (opened)="initFilterAvisModelList()">
<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>
......@@ -50,7 +50,8 @@
[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 *ngIf="getCurrentAvisUserIndex() === i && !adminMode" class="fa fa-chevron-right fa-2x" mat-list-icon color="accent">
<mat-icon *ngIf="getCurrentAvisUserIndex() === i && !adminMode" class="fa fa-chevron-right fa-2x"
mat-list-icon color="accent">
</mat-icon>
<mat-icon
[ngClass]="{'fa fa-user fa-2x': functions.empty(diffusion.picture),'avatar': !functions.empty(diffusion.picture)}"
......@@ -58,8 +59,8 @@
[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"
<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">
......@@ -71,10 +72,16 @@
{{diffusion.item_entity}}
</div>
<div *ngIf="diffusion.process_date != null" class="workflowLineProcessDate"
title='{{diffusion.process_date | fullDate}}'
color="accent">{{lang.avisSent}} {{diffusion.process_date
title='{{diffusion.process_date | fullDate}}' color="accent">{{lang.avisSent}} {{diffusion.process_date
| timeAgo : 'full'}}</div>
</div>
<div *ngIf="mode === 'parallel'">
<button class="currentRoleButton" color="primary"
[disabled]="!adminMode || !functions.empty(diffusion.process_date)" mat-raised-button [matMenuTriggerFor]="menu" [title]="getRoleLabel(diffusion.item_mode)">{{getRoleLabel(diffusion.item_mode)}}</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let role of availableRoles" (click)="changeRole(role, i)">{{role.label}}</button>
</mat-menu>
</div>
</div>
<button mat-icon-button *ngIf="adminMode && functions.empty(diffusion.process_date)"
(click)="deleteItem(i)">
......
......@@ -32,6 +32,8 @@ export class AvisWorkflowComponent implements OnInit {
public: []
};
availableRoles: any[] = [];
signAvisUsers: any = [];
filteredSignAvisUsers: Observable<string[]>;
filteredPublicModels: Observable<string[]>;
......@@ -45,6 +47,8 @@ export class AvisWorkflowComponent implements OnInit {
@Input('adminMode') adminMode: boolean;
@Input('resId') resId: number = null;
@Input('mode') mode: 'parallel' | 'circuit' = 'circuit';
@ViewChild('searchAvisUserInput', { static: true }) searchAvisUserInput: ElementRef;
searchAvisUser = new FormControl();
......@@ -58,6 +62,10 @@ export class AvisWorkflowComponent implements OnInit {
) { }
ngOnInit(): void {
if (this.mode === 'parallel') {
this.loadAvisRoles();
}
if (this.resId !== null) {
this.loadWorkflow(this.resId);
}
......@@ -73,6 +81,30 @@ export class AvisWorkflowComponent implements OnInit {
}
}
loadAvisRoles() {
return new Promise((resolve, reject) => {
this.http.get(`../../rest/roles`).pipe(
tap((data:any) => {
this.availableRoles = data.roles.filter((role: any) => ['avis', 'avis_copy', 'avis_info'].indexOf(role.id) > -1).map((role: any) => {
return {
id: role.id,
label: role.label
}
});
resolve(true);
}),
catchError((err: any) => {
this.notify.handleErrors(err);
return of(false);
})
).subscribe();
});
}
getRoleLabel(id: string) {
return this.availableRoles.filter(role => role.id === id) [0].label;
}
loadListModel(entityId: number) {
this.loading = true;
......@@ -240,7 +272,7 @@ export class AvisWorkflowComponent implements OnInit {
this.avisWorkflow.items.push(
{
...element,
difflist_type: 'AVIS_CIRCUIT'
difflist_type: this.mode === 'circuit' ? 'AVIS_CIRCUIT' : 'entity_id'
});
});
this.avisWorkflowClone = JSON.parse(JSON.stringify(this.avisWorkflow.items))
......@@ -263,7 +295,7 @@ export class AvisWorkflowComponent implements OnInit {
this.avisWorkflow.items.push(
{
...element,
difflist_type: 'AVIS_CIRCUIT'
difflist_type: this.mode === 'circuit' ? 'AVIS_CIRCUIT' : 'entity_id'
});
});
this.avisWorkflowClone = JSON.parse(JSON.stringify(this.avisWorkflow.items))
......@@ -284,8 +316,8 @@ export class AvisWorkflowComponent implements OnInit {
return this.avisWorkflow.items.length;
}
changeRole(i: number) {
this.avisWorkflow.items[i].requested_signature = !this.avisWorkflow.items[i].requested_signature;
changeRole(role: any, i : number) {
this.avisWorkflow.items[i].item_mode = role.id;
}
getWorkflow() {
......@@ -365,11 +397,10 @@ export class AvisWorkflowComponent implements OnInit {
item_id: item.id,
item_type: 'user',
item_entity: item.entity,
item_mode: 'avis',
labelToDisplay: item.label,
externalId: !this.functions.empty(item.externalId) ? item.externalId : null,
difflist_type: 'AVIS_CIRCUIT',
signatory: false,
requested_signature: false
difflist_type: this.mode === 'circuit' ? 'AVIS_CIRCUIT' : 'entity_id'
});
this.searchAvisUser.reset();
} else if (item.type === 'entity') {
......@@ -382,9 +413,8 @@ export class AvisWorkflowComponent implements OnInit {
item_type: 'user',
labelToDisplay: itemTemplate.idToDisplay,
item_entity: itemTemplate.descriptionToDisplay,
difflist_type: 'AVIS_CIRCUIT',
signatory: false,
requested_signature: false
item_mode: 'avis',
difflist_type: this.mode === 'circuit' ? 'AVIS_CIRCUIT' : 'entity_id'
}
})
);
......
......@@ -1420,4 +1420,5 @@ export const LANG_EN = {
"noCircuitAvailable": "No workflow defined",
"interrupted": "Interrupted",
"addOpinionReason": "Please fill the opinion reason",
"opinionLimitDate": "Opinion limit date",
};
......@@ -1460,4 +1460,5 @@ export const LANG_FR = {
"noCircuitAvailable": "Aucun circuit défini",
"interrupted": "Interrompu",
"addOpinionReason": "Veuillez renseigner le motif de l'avis",
"opinionLimitDate": "Date limite de l'avis",
};
......@@ -1445,4 +1445,5 @@ export const LANG_NL = {
"noCircuitAvailable": "No workflow defined", //_TO_TRANSLATE
"interrupted": "Interrupted", //_TO_TRANSLATE
"addOpinionReason": "Please fill the opinion reason", //_TO_TRANSLATE
"opinionLimitDate": "Opinion limit date", //_TO_TRANSLATE
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment