diff --git a/migration/20.03/migrateCustomValues.php b/migration/20.03/migrateCustomValues.php
index 1fe3fc1af85b4d0cffbdf39c0ae3ed85e3332d6f..6f39176284ab03c4b3b7e1ae805ef14ccea553e1 100644
--- a/migration/20.03/migrateCustomValues.php
+++ b/migration/20.03/migrateCustomValues.php
@@ -49,6 +49,9 @@ foreach ($customs as $custom) {
             ]);
 
             $csColumn = "custom_fields->>''{$fieldId}''";
+            if ($type == 'date') {
+                $csColumn = "($csColumn)::date";
+            }
             \Basket\models\BasketModel::update(['postSet' => ['basket_clause' => "REPLACE(basket_clause, '{$migration['id']}', '{$csColumn}')"], 'where' => ['1 = ?'], 'data' => [1]]);
 
             foreach ($migration['modelId'] as $modelId) {
diff --git a/sql/data_fr.sql b/sql/data_fr.sql
index 6d5dbce664040f9199d63afb8d44e4a33af6d095..cd0713f8fba395e39151b777f1b9e9174ddedb2e 100755
--- a/sql/data_fr.sql
+++ b/sql/data_fr.sql
@@ -1270,7 +1270,7 @@ INSERT INTO tags (label) VALUES ('SPORT');
 --TEMPLATES
 ------------
 TRUNCATE TABLE templates;
-INSERT INTO templates (template_id, template_label, template_comment, template_content, template_type, template_path, template_file_name, template_style, template_datasource, template_target, template_attachment_type) VALUES (3, 'Appel téléphonique', 'Appel Téléphonique', '', 'OFFICE', '0000#', 'appel_telephonique.docx', 'ODT: invitation', 'letterbox_attachment', 'indexingFile', 'all');
+--INSERT INTO templates (template_id, template_label, template_comment, template_content, template_type, template_path, template_file_name, template_style, template_datasource, template_target, template_attachment_type) VALUES (3, 'Appel téléphonique', 'Appel Téléphonique', '', 'OFFICE', '0000#', 'appel_telephonique.docx', 'ODT: invitation', 'letterbox_attachment', 'indexingFile', 'all');
 INSERT INTO templates (template_id, template_label, template_comment, template_content, template_type, template_path, template_file_name, template_style, template_datasource, template_target, template_attachment_type) VALUES (2, '[notification] Notifications événement', 'Notifications des événements système', '<p><font face="verdana,geneva" size="1">Bonjour [recipient.firstname] [recipient.lastname],</font></p>
 <p><font face="verdana,geneva" size="1"> </font></p>
 <p><font face="verdana,geneva" size="1">Voici la liste des &eacute;v&eacute;nements de l''application qui vous sont notifi&eacute;s ([notification.description]) :</font></p>
diff --git a/src/app/action/controllers/AcknowledgementReceiptTrait.php b/src/app/action/controllers/AcknowledgementReceiptTrait.php
index 69f1a0482d12ba929cd172c492b44311987536d8..193ad93d6b5396bbbfa0edf542a39299324aeb51 100644
--- a/src/app/action/controllers/AcknowledgementReceiptTrait.php
+++ b/src/app/action/controllers/AcknowledgementReceiptTrait.php
@@ -21,6 +21,7 @@ use Docserver\models\DocserverModel;
 use Doctype\models\DoctypeModel;
 use Email\controllers\EmailController;
 use Entity\models\EntityModel;
+use Group\controllers\PrivilegeController;
 use Resource\models\ResModel;
 use Resource\models\ResourceContactModel;
 use SrcCore\models\DatabaseModel;
@@ -179,11 +180,25 @@ trait AcknowledgementReceiptTrait
         if (!empty($emailsToSend) && !empty($resource['destination'])) {
             $entity = EntityModel::getByEntityId(['entityId' => $resource['destination'], 'select' => ['email', 'id']]);
         }
+
+        if (empty($entity['email']) || !PrivilegeController::hasPrivilege(['privilegeId' => 'use_mail_services', 'userId' =>  $currentUser['id']])) {
+            $emailSender = ['email' => $currentUser['mail']];
+        } else {
+            $availableEmails = EmailController::getAvailableEmailsByUserId(['userId' => $currentUser['id']]);
+            $entities = array_column($availableEmails, 'entityId');
+
+            if (!in_array( $entity['id'], $entities)) {
+                $emailSender = ['email' => $currentUser['mail']];
+            } else {
+                $emailSender = ['email' => $entity['email'], 'entityId' => $entity['id']];
+            }
+        }
+
         foreach ($emailsToSend as $email) {
             $isSent = EmailController::createEmail([
                 'userId'    => $currentUser['id'],
                 'data'      => [
-                    'sender'        => empty($entity['email']) ? ['email' => $currentUser['mail']] : ['email' => $entity['email'], 'entityId' => $entity['id']],
+                    'sender'        => $emailSender,
                     'recipients'    => [$email['email']],
                     'object'        => '[AR] ' . substr($subjectToSend, 0, 100),
                     'body'          => base64_decode($email['encodedHtml']),
diff --git a/src/app/attachment/controllers/AttachmentController.php b/src/app/attachment/controllers/AttachmentController.php
index 486dd516404500ea8da8feb3431afd34f58fcd30..771a33ac87d9b4181d3c097f5a8eed8f55a044cf 100755
--- a/src/app/attachment/controllers/AttachmentController.php
+++ b/src/app/attachment/controllers/AttachmentController.php
@@ -75,7 +75,7 @@ class AttachmentController
             'tableName' => 'res_letterbox',
             'recordId'  => $body['resIdMaster'],
             'eventType' => 'ADD',
-            'info'      => _ATTACHMENT_ADDED . " : {$id}",
+            'info'      => _ATTACHMENT_ADDED . " : {$body['title']}",
             'moduleId'  => 'attachment',
             'eventId'   => 'attachmentAdd'
         ]);
@@ -210,7 +210,7 @@ class AttachmentController
             'tableName' => 'res_letterbox',
             'recordId'  => $attachment['res_id_master'],
             'eventType' => 'UP',
-            'info'      => _ATTACHMENT_UPDATED . " : {$args['id']}",
+            'info'      => _ATTACHMENT_UPDATED . " : {$body['title']}",
             'moduleId'  => 'attachment',
             'eventId'   => 'attachmentModification'
         ]);
@@ -253,6 +253,15 @@ class AttachmentController
             'eventId'   => 'attachmentSuppression',
         ]);
 
+        HistoryController::add([
+            'tableName' => 'res_letterbox',
+            'recordId'  => $attachment['res_id_master'],
+            'eventType' => 'DEL',
+            'info'      => _ATTACHMENT_DELETED . " : {$attachment['title']}",
+            'moduleId'  => 'attachment',
+            'eventId'   => 'attachmentAdd'
+        ]);
+
         return $response->withStatus(204);
     }
 
@@ -321,7 +330,7 @@ class AttachmentController
 
     public function setInSignatureBook(Request $request, Response $response, array $aArgs)
     {
-        $attachment = AttachmentModel::getById(['id' => $aArgs['id'], 'select' => ['in_signature_book', 'res_id_master']]);
+        $attachment = AttachmentModel::getById(['id' => $aArgs['id'], 'select' => ['in_signature_book', 'res_id_master', 'title']]);
         if (empty($attachment)) {
             return $response->withStatus(400)->withJson(['errors' => 'Attachment not found']);
         }
@@ -332,12 +341,30 @@ class AttachmentController
 
         AttachmentModel::setInSignatureBook(['id' => $aArgs['id'], 'inSignatureBook' => !$attachment['in_signature_book']]);
 
+        $info = $attachment['in_signature_book'] ? _ATTACH_REMOVE_FROM_SIGNATORY_BOOK : _ATTACH_ADD_TO_SIGNATORY_BOOK;
+        HistoryController::add([
+            'tableName' => 'res_attachments',
+            'recordId'  => $aArgs['id'],
+            'eventType' => 'UP',
+            'info'      => $info . " : {$attachment['title']}",
+            'moduleId'  => 'attachment',
+            'eventId'   => 'attachmentModification',
+        ]);
+        HistoryController::add([
+            'tableName' => 'res_letterbox',
+            'recordId'  => $attachment['res_id_master'],
+            'eventType' => 'UP',
+            'info'      => $info . " : " . $attachment['title'],
+            'moduleId'  => 'resource',
+            'eventId'   => 'resourceModification',
+        ]);
+
         return $response->withJson(['success' => 'success']);
     }
 
     public function setInSendAttachment(Request $request, Response $response, array $aArgs)
     {
-        $attachment = AttachmentModel::getById(['id' => $aArgs['id'], 'select' => ['in_send_attach', 'res_id_master']]);
+        $attachment = AttachmentModel::getById(['id' => $aArgs['id'], 'select' => ['in_send_attach', 'res_id_master', 'title']]);
         if (empty($attachment)) {
             return $response->withStatus(400)->withJson(['errors' => 'Attachment not found']);
         }
@@ -348,6 +375,24 @@ class AttachmentController
 
         AttachmentModel::setInSendAttachment(['id' => $aArgs['id'], 'inSendAttachment' => !$attachment['in_send_attach']]);
 
+        $info = $attachment['in_send_attach'] ? _ATTACH_REMOVE_FROM_SHIPPING : _ATTACH_ADD_TO_SHIPPING;
+        HistoryController::add([
+            'tableName' => 'res_attachments',
+            'recordId'  => $aArgs['id'],
+            'eventType' => 'UP',
+            'info'      => $info . " : {$attachment['title']}",
+            'moduleId'  => 'attachment',
+            'eventId'   => 'attachmentModification',
+        ]);
+        HistoryController::add([
+            'tableName' => 'res_letterbox',
+            'recordId'  => $attachment['res_id_master'],
+            'eventType' => 'UP',
+            'info'      => $info . " : " . $attachment['title'],
+            'moduleId'  => 'resource',
+            'eventId'   => 'resourceModification',
+        ]);
+
         return $response->withJson(['success' => 'success']);
     }
 
@@ -420,7 +465,7 @@ class AttachmentController
         }
 
         $attachment = AttachmentModel::get([
-            'select'    => ['res_id', 'docserver_id', 'res_id_master', 'format'],
+            'select'    => ['res_id', 'docserver_id', 'res_id_master', 'format', 'title'],
             'where'     => ['res_id = ?', 'status not in (?)'],
             'data'      => [$args['id'], ['DEL']],
             'limit'     => 1
@@ -469,10 +514,19 @@ class AttachmentController
             'recordId'  => $args['id'],
             'eventType' => 'VIEW',
             'info'      => _ATTACH_DISPLAYING . " : {$args['id']}",
-            'moduleId'  => 'attachments',
+            'moduleId'  => 'attachment',
             'eventId'   => 'resview',
         ]);
 
+        HistoryController::add([
+            'tableName' => 'res_letterbox',
+            'recordId'  => $attachment['res_id_master'],
+            'eventType' => 'VIEW',
+            'info'      => _ATTACH_DISPLAYING . " : {$attachment['title']}",
+            'moduleId'  => 'attachment',
+            'eventId'   => 'resview'
+        ]);
+
         $data = $request->getQueryParams();
         if ($data['mode'] == 'base64') {
             return $response->withJson(['encodedDocument' => base64_encode($fileContent), 'originalFormat' => $attachment['format']]);
@@ -494,7 +548,7 @@ class AttachmentController
         }
 
         $attachment = AttachmentModel::get([
-            'select'    => ['res_id', 'docserver_id', 'path', 'filename', 'res_id_master'],
+            'select'    => ['res_id', 'docserver_id', 'path', 'filename', 'res_id_master', 'title'],
             'where'     => ['res_id = ?', 'status not in (?)'],
             'data'      => [$args['id'], ['DEL']],
             'limit'     => 1
@@ -551,10 +605,19 @@ class AttachmentController
             'recordId'  => $args['id'],
             'eventType' => 'VIEW',
             'info'      => _ATTACH_DISPLAYING . " : {$id}",
-            'moduleId'  => 'attachments',
+            'moduleId'  => 'attachment',
             'eventId'   => 'resview',
         ]);
 
+        HistoryController::add([
+            'tableName' => 'res_letterbox',
+            'recordId'  => $attachmentTodisplay['res_id_master'],
+            'eventType' => 'VIEW',
+            'info'      => _ATTACH_DISPLAYING . " : {$attachmentTodisplay['title']}",
+            'moduleId'  => 'attachment',
+            'eventId'   => 'resview'
+        ]);
+
         return $response->withHeader('Content-Type', $mimeType);
     }
 
diff --git a/src/app/email/controllers/EmailController.php b/src/app/email/controllers/EmailController.php
index 443a6c449e26ecdfe2614630eb7d95b017b9ba22..90d5b3ff1d6e0614b894e1e97c689392856f9571 100644
--- a/src/app/email/controllers/EmailController.php
+++ b/src/app/email/controllers/EmailController.php
@@ -672,7 +672,7 @@ class EmailController
         return ['success' => 'success'];
     }
 
-    private static function getAvailableEmailsByUserId(array $args)
+    public static function getAvailableEmailsByUserId(array $args)
     {
         $currentUser = UserModel::getById(['select' => ['firstname', 'lastname', 'mail', 'user_id'], 'id' => $args['userId']]);
 
diff --git a/src/app/resource/controllers/ResController.php b/src/app/resource/controllers/ResController.php
index 57cb3f72f2ec0333c74abb3c87d6de9896015c63..287f2fa7621e99c609307daf18ecffb174b21b6e 100755
--- a/src/app/resource/controllers/ResController.php
+++ b/src/app/resource/controllers/ResController.php
@@ -795,6 +795,13 @@ class ResController extends ResourceControlController
             return $response->withStatus(400)->withJson(['errors' => 'Body param integrations is missing or not an array']);
         }
 
+        $documents = ResModel::get([
+            'select' => ['alt_identifier', 'res_id'],
+            'where'  => ['res_id in (?)'],
+            'data'   => [$body['resources']]
+        ]);
+        $documents = array_column($documents, 'alt_identifier', 'res_id');
+
         if (isset($body['integrations']['inSignatureBook']) && Validator::boolType()->validate($body['integrations']['inSignatureBook'])) {
             $inSignatureBook = $body['integrations']['inSignatureBook'] ? 'true' : 'false';
 
@@ -805,6 +812,18 @@ class ResController extends ResourceControlController
                 'where' => ['res_id in (?)'],
                 'data'  => [$body['resources']]
             ]);
+
+            $info = $body['integrations']['inSignatureBook'] ? _DOC_ADD_TO_SIGNATORY_BOOK : _DOC_REMOVE_FROM_SIGNATORY_BOOK;
+            foreach ($body['resources'] as $resId) {
+                HistoryController::add([
+                    'tableName' => 'res_letterbox',
+                    'recordId'  => $resId,
+                    'eventType' => 'UP',
+                    'info'      => $info . " : " . $documents[$resId],
+                    'moduleId'  => 'resource',
+                    'eventId'   => 'resourceModification',
+                ]);
+            }
         }
 
         if (isset($body['integrations']['inShipping']) && Validator::boolType()->validate($body['integrations']['inShipping'])) {
@@ -817,6 +836,18 @@ class ResController extends ResourceControlController
                 'where' => ['res_id in (?)'],
                 'data'  => [$body['resources']]
             ]);
+
+            $info = $body['integrations']['inShipping'] ? _DOC_ADD_TO_MAILEVA : _DOC_REMOVE_FROM_MAILEVA;
+            foreach ($body['resources'] as $resId) {
+                HistoryController::add([
+                    'tableName' => 'res_letterbox',
+                    'recordId'  => $resId,
+                    'eventType' => 'UP',
+                    'info'      => $info . " : " . $documents[$resId],
+                    'moduleId'  => 'resource',
+                    'eventId'   => 'resourceModification',
+                ]);
+            }
         }
 
         return $response->withStatus(204);
diff --git a/src/app/resource/controllers/ResourceControlController.php b/src/app/resource/controllers/ResourceControlController.php
index ceada524ae0e5dc87b9c2d1d562908b237f03489..23ec692463af37e955f498ca0c6924f07be8da1d 100644
--- a/src/app/resource/controllers/ResourceControlController.php
+++ b/src/app/resource/controllers/ResourceControlController.php
@@ -383,8 +383,8 @@ class ResourceControlController
                         }
                     } elseif ($customField['type'] == 'string' && !Validator::stringType()->notEmpty()->validate($body['customFields'][$customFieldId])) {
                         return ['errors' => "Body customFields[{$customFieldId}] is not a string"];
-                    } elseif ($customField['type'] == 'integer' && !Validator::intVal()->notEmpty()->validate($body['customFields'][$customFieldId])) {
-                        return ['errors' => "Body customFields[{$customFieldId}] is not an integer"];
+                    } elseif ($customField['type'] == 'integer' && !Validator::floatVal()->notEmpty()->validate($body['customFields'][$customFieldId])) {
+                        return ['errors' => "Body customFields[{$customFieldId}] is not a number"];
                     } elseif ($customField['type'] == 'date' && !Validator::date()->notEmpty()->validate($body['customFields'][$customFieldId])) {
                         return ['errors' => "Body customFields[{$customFieldId}] is not a date"];
                     }
diff --git a/src/core/lang/lang-en.php b/src/core/lang/lang-en.php
index 15e7ee0311c669727acd1b2f658aeb3072c16ca5..b14c03c0644bc62061e8f6c07bcd4a92ce149254 100755
--- a/src/core/lang/lang-en.php
+++ b/src/core/lang/lang-en.php
@@ -470,3 +470,13 @@ define("_SENT_BY", "Sent by");
 define('_TO_CCI', 'Copy hidden');
 define('_PRIMARY_INFORMATION', 'Primary information');
 define("_EMPTY_SUBJECT", "Empty subject");
+
+define("_ATTACH_REMOVE_FROM_SIGNATORY_BOOK", "Attachment removed from signatory book");
+define("_ATTACH_ADD_TO_SIGNATORY_BOOK", "Attachment added to signatory book");
+define("_ATTACH_REMOVE_FROM_SHIPPING", "Attachment removed from Maileva shippings");
+define("_ATTACH_ADD_TO_SHIPPING", "Attachment added to Maileva shippings");
+
+define("_DOC_ADD_TO_SIGNATORY_BOOK", "Mail added to signatory book");
+define("_DOC_REMOVE_FROM_SIGNATORY_BOOK", "Mail removed from signatory book");
+define("_DOC_ADD_TO_MAILEVA", "Mail added to Maileva shippings");
+define("_DOC_REMOVE_FROM_MAILEVA", "Mail removed from Maileva shippings");
diff --git a/src/core/lang/lang-fr.php b/src/core/lang/lang-fr.php
index e666d6cfe6ecf21adb2d4624269ecb7b2e890397..a837b49212ee4dcd44e6bc2028c52ef28099ed9f 100755
--- a/src/core/lang/lang-fr.php
+++ b/src/core/lang/lang-fr.php
@@ -470,3 +470,13 @@ define("_SENT_BY", "Envoyé par");
 define('_TO_CCI', 'En copie caché');
 define('_PRIMARY_INFORMATION', 'Informations principales');
 define("_EMPTY_SUBJECT", "Objet vide");
+
+define("_ATTACH_REMOVE_FROM_SIGNATORY_BOOK", "Pièce jointe retirée du parapheur électronique");
+define("_ATTACH_ADD_TO_SIGNATORY_BOOK", "Pièce jointe intégrée au parapheur électronique");
+define("_ATTACH_REMOVE_FROM_SHIPPING", "Pièce jointe retirée des envois Maileva");
+define("_ATTACH_ADD_TO_SHIPPING", "Pièce jointe intégrée aux envois Maileva");
+
+define("_DOC_ADD_TO_SIGNATORY_BOOK", "Courrier intégré au parapheur électronique");
+define("_DOC_REMOVE_FROM_SIGNATORY_BOOK", "Courrier retiré du parapheur électronique");
+define("_DOC_ADD_TO_MAILEVA", "Courrier intégré aux envois Maileva");
+define("_DOC_REMOVE_FROM_MAILEVA", "Courrier retiré des envois Maileva");
diff --git a/src/core/lang/lang-nl.php b/src/core/lang/lang-nl.php
index aa72ded1831f9bb3caa57f5c91cd7aae6d2f29c1..1b4a7c791f378b9f02ea7bcda502d176749c85b3 100755
--- a/src/core/lang/lang-nl.php
+++ b/src/core/lang/lang-nl.php
@@ -473,3 +473,13 @@ define("_SENT_BY", "Sent by"); //TO TRANSLATE
 define('_TO_CCI', 'On copy hidden'); //TO TRANSLATE
 define('_PRIMARY_INFORMATION', 'Primary information'); //TO TRANSLATE
 define("_EMPTY_SUBJECT", "Empty subject"); //TO TRANSLATE
+
+define("_ATTACH_REMOVE_FROM_SIGNATORY_BOOK", "Attachment removed from signatory book"); //TO TRANSLATE
+define("_ATTACH_ADD_TO_SIGNATORY_BOOK", "Attachment added to signatory book"); //TO TRANSLATE
+define("_ATTACH_REMOVE_FROM_SHIPPING", "Attachment removed from Maileva shippings"); //TO TRANSLATE
+define("_ATTACH_ADD_TO_SHIPPING", "Attachment added to Maileva shippings"); //TO TRANSLATE
+
+define("_DOC_ADD_TO_SIGNATORY_BOOK", "Mail added to signatory book"); //TO TRANSLATE
+define("_DOC_REMOVE_FROM_SIGNATORY_BOOK", "Mail removed from signatory book"); //TO TRANSLATE
+define("_DOC_ADD_TO_MAILEVA", "Mail added to Maileva shippings"); //TO TRANSLATE
+define("_DOC_REMOVE_FROM_MAILEVA", "Mail removed from Maileva shippings"); //TO TRANSLATE
diff --git a/src/frontend/app/attachments/attachments-page/attachment-page.component.html b/src/frontend/app/attachments/attachments-page/attachment-page.component.html
index aa9a9a6bf5e97254c27126aa2acf4369bc550ad4..c29832ce4da0e6309d6fafc61b052f63f2861630 100644
--- a/src/frontend/app/attachments/attachments-page/attachment-page.component.html
+++ b/src/frontend/app/attachments/attachments-page/attachment-page.component.html
@@ -191,7 +191,7 @@
                             </ng-template>
                             <app-document-viewer #appAttachmentViewer style="display:block;height:100%;width:100%;overflow: auto;" [editMode]="editMode"
                                 [resId]="data.resId" [resIdMaster]="attachment['resIdMaster'].value" [mode]="'attachment'"
-                                [format]="attachment['format'].value" [attachType]="attachment['type'].value" [infoPanel]="snavLeft"
+                                [format]="attachment['format'].value" [attachType]="attachment['type'].value"
                                 (triggerEvent)="setDatasViewer($event)"
                                 [title]="attachment.chrono.value + ' - ' + attachment.title.value">
                             </app-document-viewer>
diff --git a/src/frontend/app/profile.component.html b/src/frontend/app/profile.component.html
index dfb11e9c2923b7ff67ac167e2d20a4fe19e665fa..38af25256089d892771d4c3bc7c0f1d45a4f7200 100755
--- a/src/frontend/app/profile.component.html
+++ b/src/frontend/app/profile.component.html
@@ -865,7 +865,7 @@
                                         </mat-cell>
                                     </ng-container>
                                     <ng-container matColumnDef="record_id">
-                                        <mat-header-cell *matHeaderCellDef>{{lang.technicalId}}</mat-header-cell>
+                                        <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.technicalId}}</mat-header-cell>
                                         <mat-cell *matCellDef="let element">{{element.record_id}}</mat-cell>
                                     </ng-container>
                                     <ng-container matColumnDef="info">
diff --git a/src/frontend/app/profile.component.ts b/src/frontend/app/profile.component.ts
index cbded5c015f94ef2b39fc5dfb4c615eb4da99db0..a032088fc2c09c775a59cf31a3288d5766cecbb7 100755
--- a/src/frontend/app/profile.component.ts
+++ b/src/frontend/app/profile.component.ts
@@ -2,7 +2,7 @@ import { Component, OnInit, NgZone, ViewChild, QueryList, ViewChildren, Template
 import { HttpClient } from '@angular/common/http';
 import { LANG } from './translate.component';
 import { NotificationService } from './notification.service';
-import { HeaderService }        from '../service/header.service';
+import { HeaderService } from '../service/header.service';
 import { debounceTime, switchMap, distinctUntilChanged, filter, tap } from 'rxjs/operators';
 import { MatDialog, MatDialogRef } from '@angular/material/dialog';
 import { MatExpansionPanel } from '@angular/material/expansion';
@@ -14,6 +14,7 @@ import { MatTableDataSource } from '@angular/material/table';
 import { SelectionModel } from '@angular/cdk/collections';
 import { FormControl, FormGroup, Validators, AbstractControl, ValidationErrors, ValidatorFn, FormBuilder } from '@angular/forms';
 import { AppService } from '../service/app.service';
+import { FunctionsService } from '../service/functions.service';
 
 declare function $j(selector: any): any;
 
@@ -52,7 +53,7 @@ export class ProfileComponent implements OnInit {
         complexityNumber: { enabled: false, value: 0 },
         complexitySpecial: { enabled: false, value: 0 },
         renewal: { enabled: false, value: 0 },
-        historyLastUse: {enabled:false, value:0},
+        historyLastUse: { enabled: false, value: 0 },
     };
     signatureModel: any = {
         base64: "",
@@ -76,7 +77,7 @@ export class ProfileComponent implements OnInit {
     loading: boolean = false;
     selectedIndex: number = 0;
     selectedIndexContactsGrp: number = 0;
-    loadingSign : boolean = false;
+    loadingSign: boolean = false;
 
     @ViewChild('snav2', { static: true }) sidenavRight: MatSidenav;
     @ViewChild('adminMenuTemplate', { static: true }) adminMenuTemplate: TemplateRef<any>;
@@ -86,9 +87,9 @@ export class ProfileComponent implements OnInit {
     myBasketExpansionPanel: boolean = false;
     editorsList: any;
     masterToggleBaskets(event: any) {
-        if (event.checked) {  
+        if (event.checked) {
             this.user.baskets.forEach((basket: any) => {
-                if ( !basket.userToDisplay) {
+                if (!basket.userToDisplay) {
                     this.selectionBaskets.select(basket);
                 }
             });
@@ -101,10 +102,10 @@ export class ProfileComponent implements OnInit {
 
     //Groups contacts
     contactsGroups: any[] = [];
-    displayedColumnsGroupsList: string[] = ['label', 'description','nbContacts','public', 'actions'];
+    displayedColumnsGroupsList: string[] = ['label', 'description', 'nbContacts', 'public', 'actions'];
     dataSourceGroupsList: any;
     @ViewChild('paginatorGroupsList', { static: false }) paginatorGroupsList: MatPaginator;
-    @ViewChild('tableGroupsListSort', { static: true }) sortGroupsList: MatSort;
+    @ViewChild('tableGroupsListSort', { static: false }) sortGroupsList: MatSort;
     applyFilterGroupsList(filterValue: string) {
         filterValue = filterValue.trim(); // Remove whitespace
         filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
@@ -142,7 +143,7 @@ export class ProfileComponent implements OnInit {
     displayedColumnsContactsList: string[] = ['contact', 'address', 'actions'];
     dataSourceContactsList: any;
     @ViewChild('paginatorContactsList', { static: false }) paginatorContactsList: MatPaginator;
-    @ViewChild('tableContactsListSort', { static: true }) sortContactsList: MatSort;
+    @ViewChild('tableContactsListSort', { static: false }) sortContactsList: MatSort;
     applyFilterContactsList(filterValue: string) {
         filterValue = filterValue.trim(); // Remove whitespace
         filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
@@ -153,7 +154,7 @@ export class ProfileComponent implements OnInit {
     displayedColumns = ['event_date', 'record_id', 'info'];
     dataSource: any;
     @ViewChild('paginatorHistory', { static: false }) paginatorHistory: MatPaginator;
-    @ViewChild('tableHistorySort', { static: true }) sortHistory: MatSort;
+    @ViewChild('tableHistorySort', { static: false }) sortHistory: MatSort;
     applyFilter(filterValue: string) {
         filterValue = filterValue.trim(); // Remove whitespace
         filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
@@ -162,14 +163,15 @@ export class ProfileComponent implements OnInit {
 
 
     constructor(
-        public http: HttpClient, 
-        private zone: NgZone, 
-        private notify: NotificationService, 
-        public dialog: MatDialog, 
-        private _formBuilder: FormBuilder, 
+        public http: HttpClient,
+        private zone: NgZone,
+        private notify: NotificationService,
+        public dialog: MatDialog,
+        private _formBuilder: FormBuilder,
         private headerService: HeaderService,
         public appService: AppService,
-        private viewContainerRef: ViewContainerRef
+        private viewContainerRef: ViewContainerRef,
+        private functions: FunctionsService
     ) {
         $j("link[href='merged_css.php']").remove();
         window['angularProfileComponent'] = {
@@ -200,23 +202,24 @@ export class ProfileComponent implements OnInit {
         if (event.index == 2) {
             if (!this.appService.getViewMode()) {
                 this.sidenavRight.open();
-            } 
-            //if (this.histories.length == 0) {
-                this.http.get('../../rest/history/users/' + this.user.id)
-                    .subscribe((data: any) => {
-                        this.histories = data.histories;
-                        setTimeout(() => {
-                            this.dataSource = new MatTableDataSource(this.histories);
-                            this.dataSource.paginator = this.paginatorHistory;
-                            this.dataSource.sort = this.sortHistory;
-                        }, 0);
-                    }, (err) => {
-                        this.notify.error(err.error.errors);
-                    });
-            //}
-        } else if(event.index == 1) {
+            }
+
+            this.http.get('../../rest/history/users/' + this.user.id)
+                .subscribe((data: any) => {
+                    this.histories = data.histories;
+                    setTimeout(() => {
+                        this.dataSource = new MatTableDataSource(this.histories);
+                        this.dataSource.sortingDataAccessor = this.functions.listSortingDataAccessor;
+                        this.dataSource.paginator = this.paginatorHistory;
+                        this.dataSource.sort = this.sortHistory;
+                    }, 0);
+                }, (err) => {
+                    this.notify.error(err.error.errors);
+                });
+
+        } else if (event.index == 1) {
             this.sidenavRight.close();
-        } else if(!this.appService.getViewMode()){
+        } else if (!this.appService.getViewMode()) {
             this.sidenavRight.open();
         }
     }
@@ -258,7 +261,7 @@ export class ProfileComponent implements OnInit {
         this.http.get('../../rest/contactsGroups')
             .subscribe((data) => {
                 this.contactsGroups = [];
-                this.contactsGroup = { public: false, contacts:[] };
+                this.contactsGroup = { public: false, contacts: [] };
                 let i = 0;
                 data['contactsGroups'].forEach((ct: any) => {
                     if (ct.owner == angularGlobals.user.id) {
@@ -292,7 +295,7 @@ export class ProfileComponent implements OnInit {
         this.http.put('../../rest/contactsGroups/' + this.contactsGroup.id, this.contactsGroup)
             .subscribe(() => {
                 this.notify.success(this.lang.contactsGroupUpdated);
-                this.initGroupsContact();    
+                this.initGroupsContact();
             }, (err) => {
                 this.notify.error(err.error.errors);
             });
@@ -411,7 +414,7 @@ export class ProfileComponent implements OnInit {
         this.http.get('../../rest/currentUser/profile')
             .subscribe((data: any) => {
                 this.user = data;
-                
+
                 this.user.baskets.forEach((value: any, index: number) => {
                     this.user.baskets[index]['disabled'] = false;
                     this.user.redirectedBaskets.forEach((value2: any) => {
@@ -472,7 +475,7 @@ export class ProfileComponent implements OnInit {
         }
     }
 
-    dndUploadSignature(event:any) {
+    dndUploadSignature(event: any) {
         if (event.mouseEvent.dataTransfer.files && event.mouseEvent.dataTransfer.files[0]) {
             var reader = new FileReader();
 
@@ -513,14 +516,14 @@ export class ProfileComponent implements OnInit {
     }
 
     addBasketRedirection(newUser: any) {
-        let basketsRedirect:any[] = [];
+        let basketsRedirect: any[] = [];
 
         this.selectionBaskets.selected.forEach((elem: any) => {
             basketsRedirect.push(
                 {
                     actual_user_id: newUser.serialId,
-                    basket_id:elem.basket_id,
-                    group_id:elem.groupSerialId,
+                    basket_id: elem.basket_id,
+                    group_id: elem.groupSerialId,
                     originalOwner: null
                 }
             )
@@ -541,7 +544,7 @@ export class ProfileComponent implements OnInit {
         }
     }
 
-    delBasketRedirection(basket: any,i: number) {
+    delBasketRedirection(basket: any, i: number) {
         let r = confirm(this.lang.confirmAction);
 
         if (r) {
@@ -556,7 +559,7 @@ export class ProfileComponent implements OnInit {
         }
     }
 
-    delBasketAssignRedirection(basket: any,i: number) {
+    delBasketAssignRedirection(basket: any, i: number) {
         let r = confirm(this.lang.confirmAction);
 
         if (r) {
@@ -576,11 +579,11 @@ export class ProfileComponent implements OnInit {
 
         if (r) {
             this.http.post("../../rest/users/" + this.user.id + "/redirectedBaskets", [
-                { 
-                    "actual_user_id": newUser.serialId, 
-                    "basket_id": basket.basket_id, 
+                {
+                    "actual_user_id": newUser.serialId,
+                    "basket_id": basket.basket_id,
                     "group_id": basket.group_id,
-                    "originalOwner": basket.owner_user_id, 
+                    "originalOwner": basket.owner_user_id,
                 }
             ])
                 .subscribe((data: any) => {
@@ -761,7 +764,7 @@ export class ProfileComponent implements OnInit {
     }
 
     updateUserPreferences() {
-        this.http.put('../../rest/currentUser/profile/preferences', {documentEdition: this.user.preferences.documentEdition})
+        this.http.put('../../rest/currentUser/profile/preferences', { documentEdition: this.user.preferences.documentEdition })
             .subscribe(() => {
                 this.notify.success(this.lang.modificationSaved);
                 this.headerService.resfreshCurrentUser();
@@ -783,7 +786,7 @@ export class ProfileComponent implements OnInit {
     changePasswd() {
         this.http.get('../../rest/passwordRules')
             .subscribe((data: any) => {
-                let valArr : ValidatorFn[] = [];
+                let valArr: ValidatorFn[] = [];
                 let ruleTextArr: String[] = [];
                 let otherRuleTextArr: String[] = [];
 
@@ -792,12 +795,12 @@ export class ProfileComponent implements OnInit {
                 data.rules.forEach((rule: any) => {
                     if (rule.label == 'minLength') {
                         this.passwordRules.minLength.enabled = rule.enabled;
-                        this.passwordRules.minLength.value = rule.value;   
+                        this.passwordRules.minLength.value = rule.value;
                         if (rule.enabled) {
                             valArr.push(Validators.minLength(this.passwordRules.minLength.value));
                             ruleTextArr.push(rule.value + ' ' + this.lang['password' + rule.label]);
                         }
-                        
+
 
                     } else if (rule.label == 'complexityUpper') {
                         this.passwordRules.complexityUpper.enabled = rule.enabled;
@@ -806,7 +809,7 @@ export class ProfileComponent implements OnInit {
                             valArr.push(this.regexValidator(new RegExp('[A-Z]'), { 'complexityUpper': '' }));
                             ruleTextArr.push(this.lang['password' + rule.label]);
                         }
-                        
+
 
                     } else if (rule.label == 'complexityNumber') {
                         this.passwordRules.complexityNumber.enabled = rule.enabled;
@@ -815,11 +818,11 @@ export class ProfileComponent implements OnInit {
                             valArr.push(this.regexValidator(new RegExp('[0-9]'), { 'complexityNumber': '' }));
                             ruleTextArr.push(this.lang['password' + rule.label]);
                         }
-                        
+
 
                     } else if (rule.label == 'complexitySpecial') {
                         this.passwordRules.complexitySpecial.enabled = rule.enabled;
-                        this.passwordRules.complexitySpecial.value = rule.value;  
+                        this.passwordRules.complexitySpecial.value = rule.value;
                         if (rule.enabled) {
                             valArr.push(this.regexValidator(new RegExp('[^A-Za-z0-9]'), { 'complexitySpecial': '' }));
                             ruleTextArr.push(this.lang['password' + rule.label]);
@@ -828,13 +831,13 @@ export class ProfileComponent implements OnInit {
                         this.passwordRules.renewal.enabled = rule.enabled;
                         this.passwordRules.renewal.value = rule.value;
                         if (rule.enabled) {
-                            otherRuleTextArr.push(this.lang['password' + rule.label] + ' <b>' + rule.value + ' ' + this.lang.days + '</b>. ' + this.lang['password2' + rule.label]+'.');
+                            otherRuleTextArr.push(this.lang['password' + rule.label] + ' <b>' + rule.value + ' ' + this.lang.days + '</b>. ' + this.lang['password2' + rule.label] + '.');
                         }
                     } else if (rule.label == 'historyLastUse') {
                         this.passwordRules.historyLastUse.enabled = rule.enabled;
                         this.passwordRules.historyLastUse.value = rule.value
                         if (rule.enabled) {
-                            otherRuleTextArr.push(this.lang['passwordhistoryLastUseDesc'] + ' <b>' + rule.value + '</b> ' + this.lang['passwordhistoryLastUseDesc2']+'.');
+                            otherRuleTextArr.push(this.lang['passwordhistoryLastUseDesc'] + ' <b>' + rule.value + '</b> ' + this.lang['passwordhistoryLastUseDesc2'] + '.');
                         }
                     }
 
@@ -846,23 +849,23 @@ export class ProfileComponent implements OnInit {
                 this.notify.error(err.error.errors);
             });
 
-            this.firstFormGroup = this._formBuilder.group({
-                newPasswordCtrl: [
-                    ''
-                ],
-                retypePasswordCtrl: [
-                    '',
-                    Validators.compose([Validators.required])
-                ],
-                currentPasswordCtrl: [
-                    '',
-                    Validators.compose([Validators.required])
-                ]
-    
-            }, {
-                    validator: this.matchValidator
-                });
-        this.validPassword =false;
+        this.firstFormGroup = this._formBuilder.group({
+            newPasswordCtrl: [
+                ''
+            ],
+            retypePasswordCtrl: [
+                '',
+                Validators.compose([Validators.required])
+            ],
+            currentPasswordCtrl: [
+                '',
+                Validators.compose([Validators.required])
+            ]
+
+        }, {
+            validator: this.matchValidator
+        });
+        this.validPassword = false;
         this.firstFormGroup.controls['currentPasswordCtrl'].setErrors(null)
         this.firstFormGroup.controls['newPasswordCtrl'].setErrors(null)
         this.firstFormGroup.controls['retypePasswordCtrl'].setErrors(null)
@@ -875,14 +878,14 @@ export class ProfileComponent implements OnInit {
         if (group.controls['newPasswordCtrl'].value == group.controls['retypePasswordCtrl'].value) {
             return false;
         } else {
-            group.controls['retypePasswordCtrl'].setErrors({'mismatch': true})
-            return {'mismatch': true};
+            group.controls['retypePasswordCtrl'].setErrors({ 'mismatch': true })
+            return { 'mismatch': true };
         }
     }
 
     getErrorMessage() {
         if (this.firstFormGroup.controls['newPasswordCtrl'].value != this.firstFormGroup.controls['retypePasswordCtrl'].value) {
-            this.firstFormGroup.controls['retypePasswordCtrl'].setErrors({'mismatch': true});
+            this.firstFormGroup.controls['retypePasswordCtrl'].setErrors({ 'mismatch': true });
         } else {
             this.firstFormGroup.controls['retypePasswordCtrl'].setErrors(null);
         }
@@ -908,12 +911,12 @@ export class ProfileComponent implements OnInit {
         }
     }
 
-    showActions(basket:any){
-        $j('#'+basket.basket_id+'_'+basket.group_id).show();
+    showActions(basket: any) {
+        $j('#' + basket.basket_id + '_' + basket.group_id).show();
     }
 
-    hideActions(basket:any){
-        $j('#'+basket.basket_id+'_'+basket.group_id).hide();
+    hideActions(basket: any) {
+        $j('#' + basket.basket_id + '_' + basket.group_id).hide();
     }
 
     toggleAddGrp() {
@@ -926,7 +929,7 @@ export class ProfileComponent implements OnInit {
         //$j('#contactsGroup').toggle();
     }
 
-    changeTabContactGrp(event:any) {
+    changeTabContactGrp(event: any) {
         this.selectedIndexContactsGrp = event;
         if (event == 0) {
             this.initGroupsContact();
diff --git a/src/frontend/app/viewer/document-viewer.component.ts b/src/frontend/app/viewer/document-viewer.component.ts
index 35b38bf8c2d19682a0af3824c8b03c7ea3378e5f..fb26ee2bc04910bc0bf917f6417300ef5369f1a4 100755
--- a/src/frontend/app/viewer/document-viewer.component.ts
+++ b/src/frontend/app/viewer/document-viewer.component.ts
@@ -7,7 +7,7 @@ import { AppService } from '../../service/app.service';
 import { tap, catchError, filter, map, exhaustMap } from 'rxjs/operators';
 import { of, Subject } from 'rxjs';
 import { ConfirmComponent } from '../../plugins/modal/confirm.component';
-import { MatDialogRef, MatDialog, MatSidenav } from '@angular/material';
+import { MatDialogRef, MatDialog } from '@angular/material';
 import { AlertComponent } from '../../plugins/modal/alert.component';
 import { SortPipe } from '../../plugins/sorting.pipe';
 import { PluginSelectSearchComponent } from '../../plugins/select-search/select-search.component';
@@ -16,7 +16,7 @@ import { EcplOnlyofficeViewerComponent } from '../../plugins/onlyoffice-api-js/o
 import { FunctionsService } from '../../service/functions.service';
 import { DocumentViewerModalComponent } from './modal/document-viewer-modal.component';
 import { PrivilegeService } from '../../service/privileges.service';
-import {VisaWorkflowModalComponent} from "../visa/modal/visa-workflow-modal.component";
+import { VisaWorkflowModalComponent } from "../visa/modal/visa-workflow-modal.component";
 
 
 @Component({
@@ -31,8 +31,53 @@ import {VisaWorkflowModalComponent} from "../visa/modal/visa-workflow-modal.comp
 
 export class DocumentViewerComponent implements OnInit {
 
+    /**
+     * document name stored in server (in tmp folder)
+     */
     @Input('tmpFilename') tmpFilename: string;
-    @Output('refreshDatas') refreshDatas = new EventEmitter<string>();
+
+    /**
+     * base64 of document  (@format is required!)
+     */
+    @Input('base64') base64: any = null;
+    @Input('format') format: string = null;
+
+    /**
+     * Target of resource (document or attachment)
+     */
+    @Input('mode') mode: 'mainDocument' | 'attachment' = 'mainDocument';
+
+    /**
+     * Resource of document or attachment (based on @mode)
+     */
+    @Input('resId') resId: number = null;
+
+
+    /**
+     * Resource of document link to attachment (@mode = 'attachment' required!)
+     */
+    @Input('resIdMaster') resIdMaster: number = null;
+
+    /**
+     * Can manage document ? (create, delete, update)
+     */
+    @Input('editMode') editMode: boolean = false;
+
+    /**
+     * Title of new tab when open document in external tab
+     */
+    @Input('title') title: string = '';
+
+
+    /**
+     * To load specific attachment type in template list (to create document)
+     */
+    @Input('attachType') attachType: string = null;
+
+    /**
+     * Event emitter
+     */
+    @Output('triggerEvent') triggerEvent = new EventEmitter<string>();
 
     lang: any = LANG;
 
@@ -64,21 +109,6 @@ export class DocumentViewerComponent implements OnInit {
 
     templateListForm = new FormControl();
 
-    @Input('base64') base64: any = null;
-    @Input('resId') resId: number = null;
-    @Input('resIdMaster') resIdMaster: number = null;
-    @Input('infoPanel') infoPanel: MatSidenav = null;
-    @Input('editMode') editMode: boolean = false;
-    @Input('title') title: string = '';
-    @Input('mode') mode: string = 'mainDocument';
-    @Input('attachType') attachType: string = null;
-    @Input('format') format: string = null;
-
-    @Output('triggerEvent') triggerEvent = new EventEmitter<string>();
-
-    private eventAction = new Subject<any>();
-
-
     resourceDatas: any;
 
     loadingInfo: any = {
@@ -856,7 +886,7 @@ export class DocumentViewerComponent implements OnInit {
                         });
                         this.listTemplates = arrValues;
                     })
-    
+
                 ).subscribe();
             } else {
                 let arrTypes: any = [];
@@ -879,9 +909,9 @@ export class DocumentViewerComponent implements OnInit {
                     }),
                     tap((data: any) => {
                         this.listTemplates = data.templates;
-    
+
                         arrTypes = arrTypes.filter((type: any) => data.templates.map((template: any) => template.attachmentType).indexOf(type.id) > -1);
-    
+
                         arrTypes.forEach((arrType: any) => {
                             arrValues.push({
                                 id: arrType.id,
@@ -901,10 +931,10 @@ export class DocumentViewerComponent implements OnInit {
                                 });
                             });
                         });
-    
+
                         this.listTemplates = arrValues;
                     })
-    
+
                 ).subscribe();
             }
         }
@@ -954,7 +984,7 @@ export class DocumentViewerComponent implements OnInit {
 
     loadTmpDocument(base64Content: string, format: string) {
         return new Promise((resolve, reject) => {
-            this.http.post(`../../rest/convertedFile/encodedFile`, { format: format, encodedFile : base64Content}).pipe(
+            this.http.post(`../../rest/convertedFile/encodedFile`, { format: format, encodedFile: base64Content }).pipe(
                 tap((data: any) => {
                     console.log(data);
                     this.file = {
@@ -989,7 +1019,7 @@ export class DocumentViewerComponent implements OnInit {
                         src: null
                     };
                 }),
-                exhaustMap((data) => this.http.post(`../../rest/convertedFile/encodedFile`, { format: data.format, encodedFile : data.content})),
+                exhaustMap((data) => this.http.post(`../../rest/convertedFile/encodedFile`, { format: data.format, encodedFile: data.content })),
                 tap((data: any) => {
                     this.file.src = this.base64ToArrayBuffer(data.encodedResource);
                     this.closeEditor();