From f616e66f1555ce867e89e15870275a0a2d7ccdc7 Mon Sep 17 00:00:00 2001 From: Alex ORLUC <alex.orluc@maarch.org> Date: Mon, 24 Feb 2020 17:51:20 +0100 Subject: [PATCH] FEAT #13120 TIME 2:45 front mailing create pj --- .../attachment-create.component.html | 34 ++- .../attachment-create.component.scss | 10 + .../attachment-create.component.ts | 272 ++++++++++++++---- .../app/viewer/document-viewer.component.ts | 1 - src/frontend/lang/lang-en.ts | 5 + src/frontend/lang/lang-fr.ts | 5 + src/frontend/lang/lang-nl.ts | 8 +- .../select-search.component.html | 2 +- .../select-search/select-search.component.ts | 2 + 9 files changed, 282 insertions(+), 57 deletions(-) diff --git a/src/frontend/app/attachments/attachment-create/attachment-create.component.html b/src/frontend/app/attachments/attachment-create/attachment-create.component.html index 60b218addda..e574f234ea9 100644 --- a/src/frontend/app/attachments/attachment-create/attachment-create.component.html +++ b/src/frontend/app/attachments/attachment-create/attachment-create.component.html @@ -6,11 +6,13 @@ <span style="flex: 1;"> {{lang.attachmentCreation}} </span> - <button *ngIf="!loading" [title]="lang.close" mat-icon-button - (click)="dialogRef.close();"> + <button *ngIf="!loading" [title]="lang.close" mat-icon-button (click)="dialogRef.close();"> <mat-icon class="fa fa-times"></mat-icon> </button></h1> <mat-dialog-content class="attach-content"> + <div *ngIf="loading" class="loading"> + <mat-spinner style="margin:auto;"></mat-spinner> + </div> <mat-tab-group #pjList style="width: 100%;" [(selectedIndex)]="indexTab" *ngIf="!loading"> <mat-tab *ngFor="let attachment of attachments; let i=index" [disabled]="isDocLoading()"> <ng-template mat-tab-label> @@ -49,8 +51,28 @@ *ngIf="attachment['title'].valid && !isEmptyField(attachment['title'])"></i> </div> </div> - <div class="attachment-form-item"> - <app-contact-autocomplete [control]="attachment['recipient']" style="width:100%;" [singleMode]="true" (retrieveDocumentEvent)="appDocumentViewer.saveDocService()"> + <div class="attachment-form-item" *ngIf="!functions.empty(attachment['recipient'])"> + <plugin-select-search [label]="lang.selectContact" + [placeholderLabel]="lang.selectContact" [datas]="resourceContacts" + [returnValue]="'object'" [formControlSelect]="selectedContact" + (afterSelected)="selectContact($event)" style="width:100%;"> + </plugin-select-search> + <div class="fieldState"> + <button mat-icon-button color="primary" + [title]="sendMassMode ? lang.disableMailing : lang.enableMailing" + [class.active]="sendMassMode" (click)="toggleSendMass()"> + <mat-icon class="fas fa-mail-bulk"></mat-icon> + </button> + <i class="fas fa-asterisk noMandatory"></i> + </div> + </div> + <div class="attachment-form-item" *ngIf="sendMassMode" style="display: block;"> + <div class="alert alert-info" role="alert" [innerHTML]="lang.mailingMsg"></div> + </div> + <div class="attachment-form-item" *ngIf="!sendMassMode"> + <app-contact-autocomplete *ngIf="!loadingContact" [control]="attachment['recipient']" + style="width:100%;" [singleMode]="true" + (retrieveDocumentEvent)="appDocumentViewer.saveDocService()"> </app-contact-autocomplete> <div class="fieldState"> <i class="fas fa-asterisk noMandatory"></i> @@ -96,7 +118,7 @@ </mat-tab> <mat-tab disabled class="addPJ"> <ng-template mat-tab-label> - <span><button mat-icon-button [title]="lang.newAttachment" + <span><button mat-icon-button [disabled]="sendMassMode" [title]="lang.newAttachment" (click)="$event.stopPropagation();newPj()"> <mat-icon class="fa fa-plus"></mat-icon> </button></span> @@ -107,6 +129,8 @@ <div mat-dialog-actions class="actions"> <button mat-raised-button color="primary" *ngIf="creationMode && !loading" (click)="onSubmit()" [disabled]="isDocLoading()">{{lang.validate}}</button> + <button mat-raised-button color="primary" *ngIf="sendMassMode && !loading" (click)="onSubmit('mailing')" + [disabled]="isDocLoading()">{{lang.mailing}}</button> <button mat-raised-button mat-button *ngIf="creationMode && !loading" [disabled]="isDocLoading()" [mat-dialog-close]="">{{lang.cancel}}</button> </div> diff --git a/src/frontend/app/attachments/attachment-create/attachment-create.component.scss b/src/frontend/app/attachments/attachment-create/attachment-create.component.scss index 2a057806bc0..9f3880ae1c0 100644 --- a/src/frontend/app/attachments/attachment-create/attachment-create.component.scss +++ b/src/frontend/app/attachments/attachment-create/attachment-create.component.scss @@ -6,6 +6,12 @@ } } +.loading { + display: flex; + height: 100%; + width: 100%; +} + .attach-container { position: relative; display: flex; @@ -100,4 +106,8 @@ background: $primary; overflow: auto; color: white; +} + +.active { + color: $secondary; } \ No newline at end of file diff --git a/src/frontend/app/attachments/attachment-create/attachment-create.component.ts b/src/frontend/app/attachments/attachment-create/attachment-create.component.ts index 1b6335f007e..7a701cb1346 100644 --- a/src/frontend/app/attachments/attachment-create/attachment-create.component.ts +++ b/src/frontend/app/attachments/attachment-create/attachment-create.component.ts @@ -10,6 +10,9 @@ import { DocumentViewerComponent } from '../../viewer/document-viewer.component' import { SortPipe } from '../../../plugins/sorting.pipe'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ConfirmComponent } from '../../../plugins/modal/confirm.component'; +import { FunctionsService } from '../../../service/functions.service'; +import { ContactService } from '../../../service/contact.service'; +import { ContactAutocompleteComponent } from '../../contact/autocomplete/contact-autocomplete.component'; @Component({ templateUrl: "attachment-create.component.html", @@ -17,7 +20,7 @@ import { ConfirmComponent } from '../../../plugins/modal/confirm.component'; 'attachment-create.component.scss', '../../indexation/indexing-form/indexing-form.component.scss' ], - providers: [AppService, SortPipe], + providers: [AppService, SortPipe, ContactService], }) export class AttachmentCreateComponent implements OnInit { @@ -26,6 +29,8 @@ export class AttachmentCreateComponent implements OnInit { loading: boolean = true; + sendMassMode: boolean = false; + sendingData: boolean = false; attachmentsTypes: any[] = []; @@ -40,10 +45,17 @@ export class AttachmentCreateComponent implements OnInit { indexTab: number = 0; + resourceContacts: any[] = []; + + selectedContact = new FormControl(); + + loadingContact: boolean = false; + @Input('resId') resId: number = null; @ViewChildren('appDocumentViewer') appDocumentViewer: QueryList<DocumentViewerComponent>; + @ViewChildren('contactAutocomplete') contactAutocomplete: ContactAutocompleteComponent; constructor( public http: HttpClient, @@ -52,41 +64,153 @@ export class AttachmentCreateComponent implements OnInit { public appService: AppService, private notify: NotificationService, private sortPipe: SortPipe, - public dialog: MatDialog) { + public dialog: MatDialog, + public functions: FunctionsService, + private contactService: ContactService, ) { } - ngOnInit(): void { - this.loadAttachmentTypes(); + async ngOnInit(): Promise<void> { + + await this.loadAttachmentTypes(); + + await this.loadResource(); + + this.loading = false; } loadAttachmentTypes() { - this.http.get('../../rest/attachmentsTypes').pipe( - tap((data: any) => { - Object.keys(data.attachmentsTypes).forEach(templateType => { - if (data.attachmentsTypes[templateType].show) { - this.attachmentsTypes.push({ - id: templateType, - ...data.attachmentsTypes[templateType] - }); + return new Promise((resolve, reject) => { + this.http.get('../../rest/attachmentsTypes').pipe( + tap((data: any) => { + Object.keys(data.attachmentsTypes).forEach(templateType => { + if (data.attachmentsTypes[templateType].show) { + this.attachmentsTypes.push({ + id: templateType, + ...data.attachmentsTypes[templateType] + }); + } + }); + this.attachmentsTypes = this.sortPipe.transform(this.attachmentsTypes, 'label'); + resolve(true) + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + this.dialogRef.close(''); + return of(false); + }) + ).subscribe(); + }); + } + + loadResource() { + return new Promise((resolve, reject) => { + this.http.get(`../../rest/resources/${this.data.resIdMaster}?light=true`).pipe( + tap(async (data: any) => { + if (!this.functions.empty(data.senders) && data.senders.length > 0) { + await this.getContacts(data.senders); } - }); - this.attachmentsTypes = this.sortPipe.transform(this.attachmentsTypes, 'label'); - }), - exhaustMap(() => this.http.get(`../../rest/resources/${this.data.resIdMaster}?light=true`)), - tap((data: any) => { - this.attachments.push({ - title: new FormControl({ value: data.subject, disabled: false }, [Validators.required]), - recipient: new FormControl({ value: '', disabled: false }), - type: new FormControl({ value: '', disabled: false }, [Validators.required]), - validationDate: new FormControl({ value: '', disabled: false }), - format: new FormControl({ value: '', disabled: false }, [Validators.required]), - encodedFile: new FormControl({ value: '', disabled: false }, [Validators.required]) - }); - this.attachFormGroup.push(new FormGroup(this.attachments[0])); - }), - finalize(() => this.loading = false) - ).subscribe(); + this.attachments.push({ + title: new FormControl({ value: data.subject, disabled: false }, [Validators.required]), + recipient: new FormControl({ value: !this.functions.empty(data.senders) ? [{ id: data.senders[0].id, type: data.senders[0].type }] : '', disabled: false }), + type: new FormControl({ value: '', disabled: false }, [Validators.required]), + validationDate: new FormControl({ value: '', disabled: false }), + format: new FormControl({ value: '', disabled: false }, [Validators.required]), + encodedFile: new FormControl({ value: '', disabled: false }, [Validators.required]) + }); + + this.attachFormGroup.push(new FormGroup(this.attachments[0])); + + if (data.senders.length > 1) { + this.toggleSendMass(); + } + + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + this.dialogRef.close(''); + return of(false); + }) + ).subscribe(); + }); + } + + async getContacts(contacts: any) { + this.resourceContacts = []; + await Promise.all(contacts.map(async (elem: any) => { + await this.getContact(elem.id, elem.type); + })); + + this.resourceContacts = this.sortPipe.transform(this.resourceContacts, 'label'); + } + + selectContact(contact: any) { + + this.loadingContact = true; + const contactChosen = JSON.parse(JSON.stringify(this.resourceContacts.filter(resContact => resContact.id === contact.id && resContact.type === contact.type)[0])); + + this.attachments[this.indexTab].recipient.setValue([contactChosen]); + + setTimeout(() => { + this.loadingContact = false; + }, 0); + + this.selectedContact.reset(); + } + + getContact(contactId: number, type: string) { + return new Promise((resolve, reject) => { + if (type === 'contact') { + this.http.get('../../rest/contacts/' + contactId).pipe( + tap((data: any) => { + this.resourceContacts.push({ + id: data.id, + type: 'contact', + label: this.contactService.formatContact(data) + }); + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + resolve(false); + return of(false); + }) + ).subscribe(); + } else if (type === 'user') { + this.http.get('../../rest/users/' + contactId).pipe( + tap((data: any) => { + this.resourceContacts.push({ + id: data.id, + type: 'user', + label: `${data.firstname} ${data.lastname}` + }); + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + resolve(false); + return of(false); + }) + ).subscribe(); + } else if (type === 'entity') { + this.http.get('../../rest/entities/' + contactId).pipe( + tap((data: any) => { + this.resourceContacts.push({ + id: data.id, + type: 'entity', + label: data.entity_label + }); + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + resolve(false); + return of(false); + }) + ).subscribe(); + } + }); } selectAttachType(attachment: any, type: any) { @@ -111,32 +235,32 @@ export class AttachmentCreateComponent implements OnInit { return formattedAttachments; } - onSubmit() { + onSubmit(mode: string = 'default') { this.appDocumentViewer.toArray()[this.indexTab].getFile().pipe( tap((data) => { this.attachments[this.indexTab].encodedFile.setValue(data.content); this.attachments[this.indexTab].format.setValue(data.format); }), - tap(() => { + tap(async () => { if (this.isValid()) { + let resId: any = null; this.sendingData = true; const attach = this.formatAttachments(); - let arrayRoutes: any = []; - this.attachments.forEach((element, index: number) => { - arrayRoutes.push(this.http.post('../../rest/attachments', attach[index])); - }); - forkJoin(arrayRoutes).pipe( - tap(() => { - this.notify.success(this.lang.attachmentAdded); - this.dialogRef.close('success'); - }), - finalize(() => this.sendingData = false), - catchError((err: any) => { - this.notify.handleErrors(err); - return of(false); - }) - ).subscribe(); + + await Promise.all(this.attachments.map(async (element: any, index: number) => { + resId = await this.saveAttachment(attach[index]); + })); + + if (this.sendMassMode && resId !== null && mode === 'mailing') { + await this.generateMailling(resId); + } + + + this.sendingData = false; + this.notify.success(this.lang.attachmentAdded); + this.dialogRef.close('success'); + } else { this.sendingData = false; this.notify.error(this.lang.mustCompleteAllAttachments); @@ -145,6 +269,38 @@ export class AttachmentCreateComponent implements OnInit { ).subscribe(); } + saveAttachment(attachment: any) { + attachment.status = this.sendMassMode ? 'SEND_MASS' : 'A_TRA'; + + return new Promise((resolve, reject) => { + this.http.post(`../../rest/attachments`, attachment).pipe( + tap((data: any) => { + resolve(data.id); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + this.dialogRef.close(''); + return of(false); + }) + ).subscribe(); + }); + } + + generateMailling(resId: number) { + return new Promise((resolve, reject) => { + this.http.post(`../../rest/attachments/${resId}/mailing`, {}).pipe( + tap(() => { + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + this.dialogRef.close(''); + return of(false); + }) + ).subscribe(); + }); + } + isValid() { let state = true; this.attachFormGroup.forEach(formgroup => { @@ -189,6 +345,9 @@ export class AttachmentCreateComponent implements OnInit { } }); datas['resId'] = this.data.resIdMaster; + if (this.sendMassMode) { + datas['inMailing'] = true; + } this.appDocumentViewer.toArray()[i].setDatas(datas); } @@ -200,18 +359,18 @@ export class AttachmentCreateComponent implements OnInit { this.attachments[this.indexTab].format.setValue(data.format); this.attachments.push({ title: new FormControl({ value: '', disabled: false }, [Validators.required]), - recipient: new FormControl({ value: null, disabled: false }), + recipient: new FormControl({ value: !this.functions.empty(this.resourceContacts[this.attachments.length]) ? [{ id: this.resourceContacts[this.attachments.length].id, type: this.resourceContacts[this.attachments.length].type }] : null, disabled: false }), type: new FormControl({ value: '', disabled: false }, [Validators.required]), validationDate: new FormControl({ value: null, disabled: false }), encodedFile: new FormControl({ value: '', disabled: false }, [Validators.required]), format: new FormControl({ value: '', disabled: false }, [Validators.required]) }); - + this.attachFormGroup.push(new FormGroup(this.attachments[this.attachments.length - 1])); this.indexTab = this.attachments.length - 1; }), ).subscribe(); - + } removePj(i: number) { @@ -253,4 +412,19 @@ export class AttachmentCreateComponent implements OnInit { return true; } } + + toggleSendMass() { + if (this.sendMassMode) { + this.sendMassMode = !this.sendMassMode; + this.selectedContact.enable(); + } else { + if (this.attachments.length === 1) { + this.sendMassMode = !this.sendMassMode; + this.selectedContact.disable(); + } else { + this.notify.error('Veuillez supprimer les <b>autres onglets PJ</b> avant de passer en <b>publipostage</b>.'); + } + + } + } } \ No newline at end of file diff --git a/src/frontend/app/viewer/document-viewer.component.ts b/src/frontend/app/viewer/document-viewer.component.ts index 720c433a261..a4d64c80f4f 100644 --- a/src/frontend/app/viewer/document-viewer.component.ts +++ b/src/frontend/app/viewer/document-viewer.component.ts @@ -545,7 +545,6 @@ export class DocumentViewerComponent implements OnInit { ); } else { await this.loadMainDocumentSubInformations(); - console.log(this.file.subinfos.mainDocVersions.length); if (this.file.subinfos.mainDocVersions.length > 0) { this.requestWithLoader(`../../rest/resources/${resId}/content?mode=base64`).subscribe( (data: any) => { diff --git a/src/frontend/lang/lang-en.ts b/src/frontend/lang/lang-en.ts index 61d955cdacb..4a82094cc38 100755 --- a/src/frontend/lang/lang-en.ts +++ b/src/frontend/lang/lang-en.ts @@ -1568,4 +1568,9 @@ export const LANG_EN = { "generateSeparators" : "Generate separators for each elements", "theTag" : "The tag", "willBeDeletedAndMerged" : "will be deleted and merged with the tag", + "mailing" : "Mailing", + "enableMailing" : "Enable mailing", + "disableMailing" : "Disable mailing", + "selectContact" : "select a contact", + "mailingMsg" : "<b>Mailing enbled</b> : <br><br><p>A <b>master</b> attachment will be created without merged field <b>contact</b> (attachmentRecipient).</p><p>If you click on Mailing, the attachmenets will be generated <b>NOW</b>.<br><br>If you click on Validate, They will be generated <b>AFTER</b> <b>approver</b> validation of visa circuit.</p><p><b>One</b> attachment will be generated for <b>each contact</b> linked to the mail.</p>", }; diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts index bf152e93823..be54402cb10 100755 --- a/src/frontend/lang/lang-fr.ts +++ b/src/frontend/lang/lang-fr.ts @@ -1607,4 +1607,9 @@ export const LANG_FR = { "generateSeparators" : "Générer des intercalaires entre les élements", "theTag" : "Le mot-clé", "willBeDeletedAndMerged" : "sera supprimé et fusionné avec le mot-clé", + "mailing" : "Publipostage", + "enableMailing" : "Activer le publipostage", + "disableMailing" : "Désactiver le publipostage", + "selectContact" : "Sélectionner un contact", + "mailingMsg" : "<b>Publipostage activé</b> : <br><br><p>Un attachement <b>maître</b> sera créé sans le champ de fusion <b>contact</b> (attachmentRecipient).</p><p>Si vous cliquez sur Publipostage, les attachements seront générés <b>TOUT DE SUITE</b>.<br><br>Si vous cliquez sur Valider, ils seront générés <b>APRÈS</b> validation des <b>viseurs</b> du circuit de visa.</p><p><b>Un</b> attachement sera généré <b>par contact</b> associé au courrier.</p>", }; diff --git a/src/frontend/lang/lang-nl.ts b/src/frontend/lang/lang-nl.ts index 558abea6a9c..f9506624206 100755 --- a/src/frontend/lang/lang-nl.ts +++ b/src/frontend/lang/lang-nl.ts @@ -1592,5 +1592,11 @@ export const LANG_NL = { "attachSummarySheet" : "Attach the summary sheet", //_TO_TRANSLATE "generateSeparators" : "Generate separators for each elements", //_TO_TRANSLATE "theTag" : "The tag",//_TO_TRANSLATE - "willBeDeletedAndMerged" : "will be deleted and merged with the tag",//_TO_TRANSLATE + "willBeDeletedAndMerged" : "will be deleted and merged with the tag", //_TO_TRANSLATE + "mailing" : "Mailing", //_TO_TRANSLATE + "enableMailing" : "Enable mailing", //_TO_TRANSLATE + "disableMailing" : "Disable mailing", //_TO_TRANSLATE + "selectContact" : "select a contact", //_TO_TRANSLATE + "mailingMsg" : "<b>Mailing enbled</b> : <br><br><p>A <b>master</b> attachment will be created without merged field <b>contact</b> (attachmentRecipient).</p><p>If you click on Mailing, the attachmenets will be generated <b>NOW</b>.<br><br>If you click on Validate, They will be generated <b>AFTER</b> <b>approver</b> validation of visa circuit.</p><p><b>One</b> attachment will be generated for <b>each contact</b> linked to the mail.</p>", //_TO_TRANSLATE + }; diff --git a/src/frontend/plugins/select-search/select-search.component.html b/src/frontend/plugins/select-search/select-search.component.html index c0b056b9aba..b7c7506383b 100644 --- a/src/frontend/plugins/select-search/select-search.component.html +++ b/src/frontend/plugins/select-search/select-search.component.html @@ -19,7 +19,7 @@ {{lang.noResult}} </div> <mat-option *ngIf="showResetOption" [value]="null"></mat-option> - <mat-option *ngFor="let value of filteredDatas | async" [value]="value.id" + <mat-option *ngFor="let value of filteredDatas | async" [value]="returnValue === 'id' ? value.id : value" [title]="value.title !== undefined ? value.title : value.label" [disabled]="value.disabled" [class.opt-group]="value.isTitle" [style.color]="value.color" [innerHTML]="value.label"> </mat-option> diff --git a/src/frontend/plugins/select-search/select-search.component.ts b/src/frontend/plugins/select-search/select-search.component.ts index 0985a223a1e..b481e098e12 100644 --- a/src/frontend/plugins/select-search/select-search.component.ts +++ b/src/frontend/plugins/select-search/select-search.component.ts @@ -27,6 +27,8 @@ export class PluginSelectSearchComponent implements OnInit, OnDestroy, AfterView @Input('datas') datas: any = []; + @Input('returnValue') returnValue: 'id' | 'object' = 'id'; + @Input('label') label: string; @Input('showResetOption') showResetOption: boolean; -- GitLab