Commit 6a7ebc29 authored by Florian Azizian's avatar Florian Azizian
Browse files

Merge branch 'develop' into 'master'

Develop

See merge request maarch/MaarchCourrier!296
parents ff3cc0c4 904d5be2
......@@ -148,6 +148,7 @@ $app->put('/watermark/configuration', \Configuration\controllers\ConfigurationCo
//Contacts
$app->get('/contacts', \Contact\controllers\ContactController::class . ':get');
$app->post('/contacts', \Contact\controllers\ContactController::class . ':create');
$app->get('/contacts/sector', \Contact\controllers\ContactController::class . ':getSectorFromAddress');
$app->get('/contacts/{id}', \Contact\controllers\ContactController::class . ':getById');
$app->put('/contacts/export', \Contact\controllers\ContactController::class . ':exportContacts');
$app->put('/contacts/import', \Contact\controllers\ContactController::class . ':importContacts');
......
......@@ -1398,6 +1398,27 @@ class ContactController
return $response->withJson($return);
}
public function getSectorFromAddress(Request $request, Response $response)
{
$queryParams = $request->getQueryParams();
if (empty($queryParams)) {
return $response->withStatus(400)->withJson(['errors' => 'Query is not set or empty']);
} elseif (!empty($queryParams['addressNumber']) && !Validator::stringType()->validate($queryParams['addressNumber'])) {
return $response->withStatus(400)->withJson(['errors' => 'Query addressNumber is not a string']);
} elseif (!empty($queryParams['addressStreet']) && !Validator::stringType()->notEmpty()->validate($queryParams['addressStreet'])) {
return $response->withStatus(400)->withJson(['errors' => 'Query addressStreet is not a string']);
} elseif (!empty($queryParams['addressPostcode']) && !Validator::stringType()->notEmpty()->validate($queryParams['addressPostcode'])) {
return $response->withStatus(400)->withJson(['errors' => 'Query addressPostcode is not a string']);
} elseif (!empty($queryParams['addressTown']) && !Validator::stringType()->notEmpty()->validate($queryParams['addressTown'])) {
return $response->withStatus(400)->withJson(['errors' => 'Query addressTown is not a string']);
}
$sector = ContactController::getAddressSector($queryParams);
return $response->withJson(['sector' => $sector]);
}
public static function getParsedContacts(array $args)
{
ValidatorModel::notEmpty($args, ['resId', 'mode']);
......@@ -1455,7 +1476,8 @@ class ContactController
'creationDate' => $contactRaw['creation_date'],
'modificationDate' => $contactRaw['modification_date'],
'customFields' => !empty($contactRaw['custom_fields']) ? json_decode($contactRaw['custom_fields'], true) : null,
'externalId' => json_decode($contactRaw['external_id'], true)
'externalId' => json_decode($contactRaw['external_id'], true),
'sector' => $contactRaw['sector']
];
if (!empty($contactRaw['communication_means'])) {
......
......@@ -238,7 +238,8 @@ class TemplateController
'template_label' => $body['label'],
'template_comment' => $body['description'],
'template_attachment_type' => $body['template_attachment_type'],
'subject' => $subject
'subject' => $subject,
'template_datasource' => $body['datasource']
];
if (!empty($body['options'])) {
if (!empty($body['options']['acknowledgementReceiptFrom']) && !in_array($body['options']['acknowledgementReceiptFrom'], ['manual', 'destination', 'mailServer', 'user' ])) {
......
......@@ -85,7 +85,7 @@
<mat-icon class="fas fa-plus" style="height: auto"></mat-icon>
</button>
<mat-menu #actionPosMenu="matMenu">
<button mat-menu-item [disabled]="!emptyDate()"
<button mat-menu-item [disabled]="!emptyDate() || !checkExternalUser()"
(click)="initDateBlock()">{{'lang.addDateBlock' | translate}}</button>
<button mat-menu-item [disabled]="!emptySign()"
(click)="initSign()">{{'lang.addSignaturePosition' | translate}}</button>
......
......@@ -262,4 +262,8 @@ export class SignaturePositionComponent implements OnInit {
})
).subscribe();
}
checkExternalUser() {
return this.data.workflow[this.currentUser].item_id !== null ? true : false;
}
}
......@@ -812,6 +812,15 @@ export class ContactsFormComponent implements OnInit {
}
});
this.checkFilling();
this.http.get('../rest/contacts/sector', {params: {'addressNumber': contact['addressNumber'], 'addressStreet': contact['addressStreet'], 'addressPostcode': contact['addressPostcode'], 'addressTown': contact['addressTown']}}).pipe(
tap((data: any) => {
if (data.sector !== null) {
const sectorIndex = this.contactForm.findIndex(element => element.id === 'sector');
this.contactForm[sectorIndex].control.setValue(data.sector.label);
this.contactForm[sectorIndex].display = true;
}
}),
).subscribe();
this.addressBANMode = disableBan ? false : true;
}
......
......@@ -136,7 +136,7 @@
</mat-header-cell>
<mat-cell *matCellDef="let element">
<mat-slide-toggle style="margin-left:11px" color="primary"
[disabled]="element.identifier == 'lastname' || element.identifier == 'company'"
[disabled]="element.identifier == 'lastname' || element.identifier == 'company' || element.identifier == 'sector'"
title="{{'lang.mandatory' | translate}}"
(change)="addCriteria($event, element, 'mandatory')" [checked]="element.mandatory">
</mat-slide-toggle>
......@@ -186,4 +186,4 @@
</div>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
\ No newline at end of file
</mat-sidenav-container>
......@@ -102,7 +102,7 @@
<mat-spinner diameter="20"></mat-spinner>
</mat-option>
<div class="autoCompleteInfoResult smallInputInfo create-contact"
*ngIf="canAdd && (noResultFound !== null || options.length > 0) && !loading" disabled>
*ngIf="canAdd && (noResultFound !== null || options.length > 0) && !loading && !fromExternalWorkflow" disabled>
<a style="cursor: pointer;" (click)="$event.stopPropagation();openContact()">
<mat-icon matSuffix class="fas fa-plus-circle" style="padding-top: 5px"></mat-icon>
{{'lang.createContact' | translate}}
......
......@@ -43,6 +43,7 @@ export class ContactAutocompleteComponent implements OnInit {
@Input() singleMode: boolean = false;
@Input() inputMode: boolean = false;
@Input() fromExternalWorkflow: boolean = false;
@Output() retrieveDocumentEvent = new EventEmitter<string>();
@Output() afterSelected = new EventEmitter<any>();
......
......@@ -94,7 +94,7 @@
</div>
</div>
</ng-container>
<ng-container *ngIf="!isModalOpen() && !loading; else elseTemplate">
<ng-container *ngIf="!isModalOpen() && !loading">
<app-history-list *ngIf="currentTool === 'history' && !loading" #appHistoryList
[resId]="currentResourceInformations.resId">
</app-history-list>
......@@ -147,7 +147,7 @@
</div>
</ng-container>
<ng-template #elseTemplate>
<ng-template *ngIf="isModalOpen() && !loading">
<div class="openedModal">
<i class="fas fa-external-link-alt"></i>
{{'lang.openedInExternalModal' | translate}}
......
......@@ -9,7 +9,7 @@
<mat-label>{{ 'lang.source' | translate }}</mat-label>
<mat-select #source [(ngModel)]="userOTP.sourceId" (selectionChange)="setCurrentSource($event.value)"
required>
<mat-option *ngFor="let source of sources" [value]="source.id">
<mat-option *ngFor="let source of sources" [value]="source.id" [title]="source.label">
{{ source.label }}
</mat-option>
</mat-select>
......@@ -17,7 +17,7 @@
<div style="display: flex;margin-bottom: 10px;" *ngIf="searchMode">
<div style="flex: 1;">
<app-contact-autocomplete [exclusion]="'?noEntities=true&noContactsGroups=true'"
[inputMode]="true" style="width:100%;padding-bottom:10px;" (afterSelected)="getContact($event)">
[inputMode]="true" style="width:100%;padding-bottom:10px;" (afterSelected)="getContact($event)" [fromExternalWorkflow]="true">
</app-contact-autocomplete>
</div>
<div>
......@@ -33,15 +33,17 @@
</div>
<div class="formType-content">
<div class="correspondent-list">
<div *ngIf="correspondentShorcuts.length === 0" class="empty">
<div *ngIf="correspondentShorcuts.length === 0; else elseBlock" class="empty">
{{'lang.noSuggestion' | translate}}
</div>
<mat-chip-list>
<mat-chip *ngFor="let shortcut of correspondentShorcuts" [title]="shortcut.title"
(click)="setOtpInfoFromShortcut(shortcut)" style="cursor: pointer;font-size: 12px;min-height: 24px;">
{{shortcut.label}}
</mat-chip>
</mat-chip-list>
<ng-template #elseBlock>
<mat-chip-list>
<mat-chip *ngFor="let shortcut of correspondentShorcuts" [title]="shortcut.title"
(click)="setOtpInfoFromShortcut(shortcut)" style="cursor: pointer;font-size: 12px;min-height: 24px;">
{{shortcut.label}}
</mat-chip>
</mat-chip-list>
</ng-template>
</div>
<div>
<button mat-icon-button color="primary" [title]="'lang.searchCorrespondent' | translate" (click)="searchMode=!searchMode">
......@@ -61,9 +63,9 @@
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>{{'lang.phoneNumber' | translate}}</mat-label>
<input matInput #phoneNumber type="text" [(ngModel)]="userOTP.phone"
(ngModelChange)="formatPhone($event)" pattern="^((\+)33)[1-9](\d{2}){4}$" required>
<mat-hint align="end">{{ 'lang.frFormatPhone' | translate }}</mat-hint>
<input matInput #phoneNumber type="text" [(ngModel)]="userOTP.phone" placeholder="Exemple: +33621235684"
[pattern]="getRegexPhone()" required>
<mat-hint align="start">{{ 'lang.frFormatPhone' | translate }}</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>{{'lang.email' | translate}}</mat-label>
......
......@@ -50,4 +50,5 @@
color: #666;
text-align: center;
font-style: italic;
overflow: hidden;
}
\ No newline at end of file
......@@ -148,7 +148,7 @@ export class CreateExternalUserComponent implements OnInit {
this.userOTP.firstname = data.firstname;
this.userOTP.lastname = data.lastname;
this.userOTP.email = data.email;
this.userOTP.phone = phone !== undefined ? phone.replace(/( |\.|\-)/g, '').replace('0', '+33') : '';
this.userOTP.phone = phone !== null ? phone.replace(/( |\.|\-)/g, '').replace('0', '+33') : '';
}),
catchError((err: any) => {
this.notify.handleSoftErrors(err);
......@@ -171,7 +171,7 @@ export class CreateExternalUserComponent implements OnInit {
data.recipients.forEach((element: any) => {
this.setCorrespondentsShorcuts(element, 'recipient');
});
} else {
} else if (data.senders !== undefined) {
data.senders.forEach((element: any) => {
this.setCorrespondentsShorcuts(element, 'sender');
});
......@@ -232,4 +232,24 @@ export class CreateExternalUserComponent implements OnInit {
this.userOTP.phone = item.phone;
}
getRegexPhone() {
// map country calling code with national number length
const phonesMap = {
'32': [8, 10], // Belgium
'33': 9, // France
'1' : 10, // United States
'27': 9 // South Africa
};
const regex = Object.keys(phonesMap).reduce((phoneFormats: any [], countryCode: any) => {
const numberLength = phonesMap[countryCode];
if (Array.isArray(numberLength)) {
phoneFormats.push('(\\+' + countryCode + `[0-9]\{${numberLength[0]},${numberLength[1]}\})`);
} else {
phoneFormats.push('(\\+' + countryCode + `[0-9]\{${numberLength}\})`);
}
return phoneFormats;
}, []).join('|');
return new RegExp(`^(${regex})$`);
};
}
<mat-list *ngIf="!loading">
<button *ngIf="adminMode && otpConfig > 0" mat-raised-button mat-button color="primary" [title]="'lang.addOtpUser' | translate"
style="margin: 10px; display: flex;" (click)="openCreateUserOtp()">
<mat-icon class="fas fa-user-plus" style="width: auto;height: auto;"></mat-icon>
{{'lang.addOtp' | translate}}
</button>
<app-plugin-autocomplete *ngIf="adminMode" [labelPlaceholder]="'lang.addPerson' | translate"
[routeDatas]="['/rest/autocomplete/maarchParapheurUsers']" [targetSearchKey]="'idToDisplay'"
[subInfoKey]="'email'" (triggerEvent)="addItemToWorkflow($event)" appearance="outline">
[subInfoKey]="'email'" (triggerEvent)="addItemToWorkflow($event)" appearance="outline"
[fromExternalWorkflow]="true" [connectorLength]="otpConfig" [resId]="resId" (updateVisaWorkflow)="updateVisaWorkflow($event)">
</app-plugin-autocomplete>
<div cdkDropList #dataAvailableList="cdkDropList" [cdkDropListData]="visaWorkflow.items" class="cdk-list"
(cdkDropListDropped)="drop($event)" [cdkDropListDisabled]="!adminMode">
......
......@@ -409,4 +409,8 @@ export class ExternalVisaWorkflowComponent implements OnInit {
).subscribe();
});
}
updateVisaWorkflow(user: any) {
this.visaWorkflow.items.push(user);
}
}
......@@ -23,6 +23,12 @@
<mat-option class="autoCompleteInfoResult smallInputInfo" *ngIf="datas.length === 0 && !loading" disabled
[innerHTML]="listInfo">
</mat-option>
<div *ngIf="fromExternalWorkflow && connectorLength > 0" class="autoCompleteInfoResult create-contact" style="font-size: 11px;">
<a style="cursor: pointer;" (click)="$event.stopPropagation(); createExternalUser()" [title]="'lang.addOtp' | translate">
<mat-icon matSuffix class="fas fa-user-plus" style="padding-top: 5px"></mat-icon>
{{'lang.addOtp' | translate}}
</a>
</div>
<mat-option *ngIf="loading" disabled>
<mat-spinner diameter="20"></mat-spinner>
</mat-option>
......
......@@ -9,6 +9,8 @@ import { HttpClient } from '@angular/common/http';
import { ConfirmComponent } from '../modal/confirm.component';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { NotificationService } from '@service/notification/notification.service';
import { CreateExternalUserComponent } from '@appRoot/visa/externalVisaWorkflow/createExternalUser/create-external-user.component';
import { ActionsService } from '@appRoot/actions/actions.service';
@Component({
selector: 'app-plugin-autocomplete',
......@@ -85,6 +87,12 @@ export class PluginAutocompleteComponent implements OnInit {
*/
@Input() styles: any = [];
@Input() fromExternalWorkflow: boolean = false;
@Input() connectorLength: number = 0;
@Input() resId: any;
@Output() updateVisaWorkflow = new EventEmitter<any>();
/**
* Catch external event after select an element in autocomplete
*/
......@@ -112,7 +120,8 @@ export class PluginAutocompleteComponent implements OnInit {
public http: HttpClient,
private notify: NotificationService,
public dialog: MatDialog,
private latinisePipe: LatinisePipe
private latinisePipe: LatinisePipe,
public actionService: ActionsService
) { }
ngOnInit() {
......@@ -293,6 +302,37 @@ export class PluginAutocompleteComponent implements OnInit {
return (offer: any) => this.displayFn(offer);
}
createExternalUser() {
const dialogRef = this.dialog.open(CreateExternalUserComponent, {
panelClass: 'maarch-modal',
disableClose: true,
width: '500px',
data: { otpInfo : null, resId : this.resId}
});
dialogRef.afterClosed().pipe(
tap(async (data: any) => {
if (data) {
const user = {
item_id: null,
item_type: 'userOtp',
labelToDisplay: `${data.otp.firstname} ${data.otp.lastname}`,
picture: await this.actionService.getUserOtpIcon(data.otp.type),
hasPrivilege: true,
isValid: true,
externalId: {
maarchParapheur: null
},
externalInformations: data.otp,
role: data.otp.role,
availableRoles: data.otp.availableRoles
};
this.updateVisaWorkflow.emit(user);
}
})
).subscribe();
}
private _filter(value: string): string[] {
if (typeof value === 'string') {
const filterValue = this.latinisePipe.transform(value.toLowerCase());
......
......@@ -55,7 +55,7 @@
<input [formControl]="recipientsInput" #copiesField [matChipInputFor]="copiesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="add($event, 'copies')" [matAutocomplete]="autoEmails2"
(focus)="resetAutocomplete()" (paste)="onPaste($event,'copies')">
(focus)="resetAutocomplete()" (paste)="onPaste($event,'copies')" [disabled]="readonly">
</mat-chip-list>
<mat-autocomplete #autoEmails2="matAutocomplete" (optionSelected)="addEmail($event.option.value, 'copies')">
<mat-option *ngFor="let option of filteredEmails | async" [value]="option">
......@@ -79,7 +79,7 @@
<input [formControl]="recipientsInput" #invisibleCopiesField [matChipInputFor]="invCopiesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="add($event, 'invisibleCopies')" [matAutocomplete]="autoEmails3"
(focus)="resetAutocomplete()" (paste)="onPaste($event,'invisibleCopies')">
(focus)="resetAutocomplete()" (paste)="onPaste($event,'invisibleCopies')" [disabled]="readonly">
</mat-chip-list>
<mat-autocomplete #autoEmails3="matAutocomplete"
(optionSelected)="addEmail($event.option.value, 'invisibleCopies')">
......
......@@ -208,6 +208,19 @@ export class MailEditorComponent implements OnInit, OnDestroy {
return new Promise((resolve) => {
this.http.get(`../rest/acknowledgementReceipts/${emailId}`).pipe(
tap((data: any) => {
this.copies = data.acknowledgementReceipt.cc.map((item: any) => ({
label: item.label,
email: item.email,
badFormat: this.isBadEmailFormat(item.email)
}));
this.showCopies = this.copies.length > 0;
this.invisibleCopies = data.acknowledgementReceipt.cci.map((item: any) => ({
label: item.label,
email: item.email,
badFormat: this.isBadEmailFormat(item.email)
}));
this.showInvisibleCopies = this.invisibleCopies.length > 0;
this.currentSender = {
label: data.acknowledgementReceipt.userLabel,
email: data.acknowledgementReceipt.userLabel
......
......@@ -2501,7 +2501,7 @@
"securityMode": "Mode d'envoi du code de sécurité",
"sms": "SMS",
"securityModeInfo": "L'utilisateur sera notifié par <b>courriel</b> et recevra un <b>code de sécurité</b> par <b>{{mode}}</b> au moment de son tour dans le circuit.",
"frFormatPhone": "Format: +33XXXXXXXXX",
"frFormatPhone": "Le numéro de téléphone doit commencer par l'indicatif de votre pays.",
"youSign": "Yousign",
"otp_visa_yousign": "Viseur (yousign)",
"otp_sign_yousign": "Signataire (yousign)",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment