From f662550da614bd5f1d3870719a5c5ce7355722c5 Mon Sep 17 00:00:00 2001
From: Damien Burel <damien.burel@maarch.org>
Date: Wed, 1 Mar 2017 17:23:49 +0100
Subject: [PATCH] FEAT #5233 Add Sign and Unsign for Signature book

---
 .../Models/ResModelAbstract.php               |  24 +-
 modules/visa/Controllers/VisaController.php   |  41 ++-
 modules/visa/Views/signatureBook.html         |   4 +-
 modules/visa/js/aController.js                | 290 +++++++++++-------
 modules/visa/sign_file.php                    |  10 +-
 rest/index.php                                |   1 +
 6 files changed, 243 insertions(+), 127 deletions(-)

diff --git a/apps/maarch_entreprise/Models/ResModelAbstract.php b/apps/maarch_entreprise/Models/ResModelAbstract.php
index 00dfc999379..fadb1f0eaed 100644
--- a/apps/maarch_entreprise/Models/ResModelAbstract.php
+++ b/apps/maarch_entreprise/Models/ResModelAbstract.php
@@ -38,6 +38,22 @@ class ResModelAbstract extends Apps_Table_Service {
         return $aReturn;
     }
 
+    public static function put(array $aArgs = []) {
+        // TODO collId stands for table in DB => à Changer pour aller récupérer la table lié à collId
+        static::checkRequired($aArgs, ['collId', 'set', 'where', 'data']);
+        static::checkString($aArgs, ['collId']);
+        static::checkArray($aArgs, ['set', 'where', 'data']);
+
+        $bReturn = static::update([
+            'table'     => $aArgs['collId'],
+            'set'       => $aArgs['set'],
+            'where'     => $aArgs['where'],
+            'data'      => $aArgs['data']
+        ]);
+
+        return $bReturn;
+    }
+
     public static function getAvailableLinkedAttachmentsIn(array $aArgs = []) {
         static::checkRequired($aArgs, ['resIdMaster', 'in']);
         static::checkNumeric($aArgs, ['resIdMaster']);
@@ -47,8 +63,8 @@ class ResModelAbstract extends Apps_Table_Service {
         $aReturn = static::select([
             'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
             'table'     => ['res_view_attachments'],
-            'where'     => ['res_id_master = ?', 'attachment_type in (?)', 'status not in (?)'],
-            'data'      => [$aArgs['resIdMaster'], $aArgs['in'], ['DEL', 'TMP', 'OBS']]
+            'where'     => ['res_id_master = ?', 'attachment_type in (?)', "status not in ('DEL', 'TMP', 'OBS')"],
+            'data'      => [$aArgs['resIdMaster'], $aArgs['in']]
         ]);
 
         return $aReturn;
@@ -63,8 +79,8 @@ class ResModelAbstract extends Apps_Table_Service {
         $aReturn = static::select([
             'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
             'table'     => ['res_view_attachments'],
-            'where'     => ['res_id_master = ?', 'attachment_type not in (?)', 'status not in (?)'],
-            'data'      => [$aArgs['resIdMaster'], $aArgs['notIn'], ['DEL', 'TMP', 'OBS']]
+            'where'     => ['res_id_master = ?', 'attachment_type not in (?)', "status not in ('DEL', 'TMP', 'OBS')"],
+            'data'      => [$aArgs['resIdMaster'], $aArgs['notIn']]
         ]);
 
         return $aReturn;
diff --git a/modules/visa/Controllers/VisaController.php b/modules/visa/Controllers/VisaController.php
index fd216d682f0..5945aa83e8f 100644
--- a/modules/visa/Controllers/VisaController.php
+++ b/modules/visa/Controllers/VisaController.php
@@ -60,12 +60,12 @@ class VisaController {
 				'res_id', 'res_id_version', 'title', 'identifier', 'attachment_type',
 				'status', 'typist', 'path', 'filename', 'updated_by', 'creation_date',
 				'validation_date', 'format', 'relation', 'dest_user', 'dest_contact_id',
-				'dest_address_id'
+				'dest_address_id', 'origin'
 			]
 		]);
 
 		foreach ($attachments as $key => $value) {
-			if ($value['attachment_type'] == 'converted_pdf') {
+			if ($value['attachment_type'] == 'converted_pdf' || ($value['attachment_type'] == 'signed_response' && !empty($value['origin']))) {
 				continue;
 			}
 
@@ -83,9 +83,20 @@ class VisaController {
 			$pathToFind = $value['path'] . str_replace(strrchr($value['filename'], '.'), '.pdf', $value['filename']);
 			foreach ($attachments as $tmpKey => $tmpValue) {
 				if ($tmpValue['attachment_type'] == 'converted_pdf' && ($tmpValue['path'] . $tmpValue['filename'] == $pathToFind)) {
-					$viewerId = $tmpValue['res_id'];
+					if ($value['status'] != 'SIGN') {
+						$viewerId = $tmpValue['res_id'];
+					}
 					unset($attachments[$tmpKey]);
 				}
+				if ($value['status'] == 'SIGN' && $tmpValue['attachment_type'] == 'signed_response' && !empty($tmpValue['origin'])) {
+					$signDaddy = explode(',', $tmpValue['origin']);
+					if (($signDaddy[0] == $value['res_id'] && $signDaddy[1] == "res_attachments")
+						|| ($signDaddy[0] == $value['res_id_version'] && $signDaddy[1] == "res_version_attachments"))
+					{
+						$viewerId = $tmpValue['res_id'];
+						unset($attachments[$tmpKey]);
+					}
+				}
 			}
 
 			if (!empty($value['dest_user'])) {
@@ -107,10 +118,12 @@ class VisaController {
 			$attachments[$key]['thumbnailLink'] = "index.php?page=doc_thumb&module=thumbnails&res_id={$realId}&coll_id={$collId}&display=true&advanced=true";
 			$attachments[$key]['viewerLink'] = "index.php?display=true&module=visa&page=view_pdf_attachement&res_id_master={$resId}&id={$viewerId}";
 
-			unset($attachments[$key]['res_id'], $attachments[$key]['res_id_version'], $attachments[$key]['path'], $attachments[$key]['filename'],
-				$attachments[$key]['dest_user'], $attachments[$key]['dest_contact_id'], $attachments[$key]['dest_address_id']);
+			unset($attachments[$key]['path'], $attachments[$key]['filename'], $attachments[$key]['dest_user'],
+				$attachments[$key]['dest_contact_id'], $attachments[$key]['dest_address_id']);
 		}
 
+		$attachments = array_values($attachments);
+
 		$incomingMailAttachments = \ResModel::getAvailableLinkedAttachmentsIn([
 			'resIdMaster' => $resId,
 			'in' 	      => ['incoming_mail_attachment'],
@@ -187,4 +200,22 @@ class VisaController {
 		return $response->withJson($datas);
 	}
 
+	public function unsignFile(RequestInterface $request, ResponseInterface $response, $aArgs) {
+
+		$resId = $aArgs['resId'];
+		$collId = $aArgs['collId'];
+
+		$bReturnSnd = false;
+		$bReturnFirst = \ResModel::put(['collId' => $collId, 'set' => ['status' => 'A_TRA'], 'where' => ['res_id = ?'], 'data' => [$resId]]);
+		if ($bReturnFirst) {
+			$bReturnSnd = \ResModel::put(['collId' => $collId, 'set' => ['status' => 'DEL'], 'where' => ['origin = ?', 'status != ?'], 'data' => [$resId . ',' .$collId, 'DEL']]);
+		}
+
+		if ($bReturnFirst && $bReturnSnd) {
+			return $response->withJson(['status' => 'OK']);
+		} else {
+			return $response->withJson(['status' => 'KO']);
+		}
+	}
+
 }
\ No newline at end of file
diff --git a/modules/visa/Views/signatureBook.html b/modules/visa/Views/signatureBook.html
index b9afd8c784b..567a5c269eb 100644
--- a/modules/visa/Views/signatureBook.html
+++ b/modules/visa/Views/signatureBook.html
@@ -101,7 +101,7 @@
                         <label>Objet : </label>
                         <span>{{signatureBook.attachments[signatureBook.rightSelectedThumbnail].title}}</span>
                     </div>
-                    <div class="infoPj" title="Laurent GIOVANONNI - 11 boulevard du sud est">
+                    <div class="infoPj" title="{{signatureBook.attachments[signatureBook.rightSelectedThumbnail].destUser}}">
                         <label>Pour : </label>
                         <span>{{signatureBook.attachments[signatureBook.rightSelectedThumbnail].destUser}}</span>
                     </div>
@@ -137,6 +137,8 @@
                    <i ng-show="signatureBook.showTopRightPanel" class="fa fa-chevron-up" aria-hidden="true"></i>
                 </div>
                 <iframe ng-src="{{signatureBook.rightViewerLink}}"></iframe>
+                <span ng-if="signatureBook.attachments[signatureBook.rightSelectedThumbnail].status != 'SIGN'" ng-click="vm.prepareSignFile(signatureBook.attachments[signatureBook.rightSelectedThumbnail])" style="cursor: pointer">SIGNER</span>
+                <span ng-if="signatureBook.attachments[signatureBook.rightSelectedThumbnail].status == 'SIGN'" ng-click="vm.unsignFile(signatureBook.attachments[signatureBook.rightSelectedThumbnail])" style="cursor: pointer">DE-SIGNER</span>
             </div>
             <div class="contentShow" ng-if="signatureBook.showAttachmentEditionPanel">
                 <span>Edition de PJ</span>
diff --git a/modules/visa/js/aController.js b/modules/visa/js/aController.js
index 602d7c15163..17a622141ea 100644
--- a/modules/visa/js/aController.js
+++ b/modules/visa/js/aController.js
@@ -1,128 +1,196 @@
 "use strict";
 
-mainApp.controller("visaCtrl", ["$scope", "$http", "$routeParams", "$interval", "NgTableParams", "$location", function($scope, $http, $routeParams, $interval, NgTableParams, $location) {
+mainApp.controller("visaCtrl", ["$scope", "$http", "$routeParams", "$interval", "NgTableParams", "$location",
+  function($scope, $http, $routeParams, $interval, NgTableParams, $location) {
 
-  var vm = this;
+    var vm = this;
 
-  function prepareSignatureBook() {
-    if (typeof globalConfig.coreurl == "undefined") {
-      InitializeJsGlobalConfig();
+    function prepareSignatureBook() {
+      if (typeof globalConfig.coreurl == "undefined") {
+        InitializeJsGlobalConfig();
+      }
+
+      $j('#inner_content').remove();
+      $j('#header').remove();
+      $j('#viewBasketsTitle').remove();
+      $j('#container').width("98%");
     }
 
-    $j('#inner_content').remove();
-    $j('#header').remove();
-    $j('#viewBasketsTitle').remove();
-    $j('#container').width("98%");
-  }
-
-  function getDatas(basketId, resId) {
-    prepareSignatureBook();
-
-    $http({
-      method : 'GET',
-      url    : globalConfig.coreurl + 'rest/' + basketId + '/signatureBook/' + resId,
-      headers: {'Content-Type': 'application/x-www-form-urlencoded'}
-    }).then(function successCallback(response) {
-
-      $scope.signatureBook = response.data;
-      $scope.signatureBook.rightSelectedThumbnail = 0;
-      $scope.signatureBook.leftSelectedThumbnail = 0;
-      if ($scope.signatureBook.attachments[0]) {
-        $scope.signatureBook.rightViewerLink = $scope.signatureBook.attachments[0].viewerLink;
+    function getDatas(basketId, resId) {
+      prepareSignatureBook();
+
+      $http({
+        method : 'GET',
+        url    : globalConfig.coreurl + 'rest/' + basketId + '/signatureBook/' + resId,
+        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+      }).then(function successCallback(response) {
+
+        $scope.signatureBook = response.data;
+        $scope.signatureBook.rightSelectedThumbnail = 0;
+        $scope.signatureBook.leftSelectedThumbnail = 0;
+        if ($scope.signatureBook.attachments[0]) {
+          $scope.signatureBook.rightViewerLink = $scope.signatureBook.attachments[0].viewerLink;
+        }
+        if ($scope.signatureBook.documents[0]) {
+          $scope.signatureBook.leftViewerLink = $scope.signatureBook.documents[0].viewerLink;
+        }
+        $scope.signatureBook.headerTab = 1;
+        $scope.signatureBook.showTopRightPanel = false;
+        $scope.signatureBook.showTopLeftPanel = false;
+        $scope.signatureBook.showAttachmentEditionPanel = false;
+
+        $scope.historyTable = new NgTableParams({
+            page: 1,
+            count: 20,
+            sorting: {
+                  event_date: 'desc'
+              }
+          }, {
+            total: $scope.signatureBook.histories.length,
+            dataset: $scope.signatureBook.histories
+          });
+
+      }, function errorCallback(error) {
+      });
+    }
+
+    $scope.changeSignatureBookLeftContent = function(id) {
+      $scope.signatureBook.headerTab = id;
+    };
+
+    $scope.changeRightViewer = function(index) {
+      if (index < 0) {
+        $scope.signatureBook.showAttachmentEditionPanel = true;
+      } else {
+        $scope.signatureBook.rightViewerLink = $scope.signatureBook.attachments[index].viewerLink;
+        $scope.signatureBook.showAttachmentEditionPanel = false;
       }
-      if ($scope.signatureBook.documents[0]) {
-        $scope.signatureBook.leftViewerLink = $scope.signatureBook.documents[0].viewerLink;
+      $scope.signatureBook.rightSelectedThumbnail = index;
+    };
+
+    $scope.changeLeftViewer = function(index) {
+      $scope.signatureBook.leftViewerLink = $scope.signatureBook.documents[index].viewerLink;
+      $scope.signatureBook.leftSelectedThumbnail = index;
+    };
+
+    $scope.displayTopPanel = function(panel) {
+      if (panel == "RIGHT") {
+        $scope.signatureBook.showTopRightPanel = !$scope.signatureBook.showTopRightPanel;
+        $scope.signatureBook.showTopRightPanel == true ? $j(".pjDetails").css("height", "100px") : $j(".pjDetails").css("height", "30px");
+      } else if (panel == "LEFT") {
+        $scope.signatureBook.showTopLeftPanel = !$scope.signatureBook.showTopLeftPanel;
+        $scope.signatureBook.showTopLeftPanel == true ? $j(".pjDoc").css("height", "100px") : $j(".pjDoc").css("height", "30px");
+        $scope.signatureBook.showTopLeftPanel == true ? $j("#leftPanelShowDocumentIframe").css("height", "80%") : $j("#leftPanelShowDocumentIframe").css("height", "90%");
       }
-      $scope.signatureBook.headerTab = 1;
-      $scope.signatureBook.showTopRightPanel = false;
-      $scope.signatureBook.showTopLeftPanel = false;
-      $scope.signatureBook.showAttachmentEditionPanel = false;
-
-      $scope.historyTable = new NgTableParams({
-          page: 1,
-          count: 20,
-          sorting: {
-                event_date: 'desc'     
-            }
-        }, {
-          total: $scope.signatureBook.histories.length,
-          dataset: $scope.signatureBook.histories
-        });
-
-    }, function errorCallback(error) {
-    });
-  }
-
-  $scope.changeSignatureBookLeftContent = function(id) {
-    $scope.signatureBook.headerTab = id;
-  };
-
-  $scope.changeRightViewer = function(index) {
-    if (index < 0) {
-      $scope.signatureBook.showAttachmentEditionPanel = true;
-    } else {
-      $scope.signatureBook.rightViewerLink = $scope.signatureBook.attachments[index].viewerLink;
-      $scope.signatureBook.showAttachmentEditionPanel = false;
-    }
-    $scope.signatureBook.rightSelectedThumbnail = index;
-  };
-
-  $scope.changeLeftViewer = function(index) {
-    $scope.signatureBook.leftViewerLink = $scope.signatureBook.documents[index].viewerLink;
-    $scope.signatureBook.leftSelectedThumbnail = index;
-  };
-
-  $scope.displayTopPanel = function(panel) {
-    if (panel == "RIGHT") {
-      $scope.signatureBook.showTopRightPanel = !$scope.signatureBook.showTopRightPanel;
-      $scope.signatureBook.showTopRightPanel == true ? $j(".pjDetails").css("height", "100px") : $j(".pjDetails").css("height", "30px");
-    } else if (panel == "LEFT") {
-      $scope.signatureBook.showTopLeftPanel = !$scope.signatureBook.showTopLeftPanel;
-      $scope.signatureBook.showTopLeftPanel == true ? $j(".pjDoc").css("height", "100px") : $j(".pjDoc").css("height", "30px");
-      $scope.signatureBook.showTopLeftPanel == true ? $j("#leftPanelShowDocumentIframe").css("height", "80%") : $j("#leftPanelShowDocumentIframe").css("height", "90%");
-    }
-  };
-
-  $scope.backToBasket = function() {
-    location.hash = "";
-    location.reload();
-  };
-
-  $scope.changeLocation = function(resId) {
-    $location.path(vm.basketId + "/signatureBook/" + resId);
-  };
-
-  $scope.validForm = function() {
-    if ($j("#signatureBookActions option:selected")[0].value != "") {
-      //$interval.cancel(intervalPromise);
-      unlockDocument($routeParams.resId);
-
-      valid_action_form(
-        'empty',
-        'http://127.0.0.1/maarch_trunk_git/apps/maarch_entreprise/index.php?display=true&page=manage_action&module=core',
-        $scope.signatureBook.currentAction,
-        $routeParams.resId,
-        'res_letterbox',
-        'null',
-        'letterbox_coll',
-        'page',
-        false,
-        [$j("#signatureBookActions option:selected")[0].value]
-      );
+    };
+
+    vm.prepareSignFile = function(attachment) {
+      if (attachment.res_id == 0) {
+        signatureBookSignFile(attachment.res_id_version, 1);
+      } else if (attachment.res_id_version == 0) {
+        signatureBookSignFile(attachment.res_id, 0);
+      }
+    };
+
+    function signatureBookSignFile(resId, type) {
+      var path = '';
+
+      if (type == 0) {
+        path = 'index.php?display=true&module=visa&page=sign_file&collId=letterbox_coll&id=' + resId;
+      } else if (type == 1) {
+        path = 'index.php?display=true&module=visa&page=sign_file&collId=letterbox_coll&isVersion&id=' + resId;
+      } else if (type == 2) {
+        path = 'index.php?display=true&module=visa&page=sign_file&collId=letterbox_coll&isOutgoing&id=' + resId;
+      }
+
+      $http({
+        method : 'GET',
+        url    : path,
+        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+      }).then(function successCallback(response) {
+
+        if (response.data.status == 0) {
+          $scope.signatureBook.rightViewerLink = "index.php?display=true&module=visa&page=view_pdf_attachement&res_id_master=" + vm.resId + "&id=" + response.data.new_id;
+          $scope.signatureBook.attachments[$scope.signatureBook.rightSelectedThumbnail].viewerLink = $scope.signatureBook.rightViewerLink;
+          $scope.signatureBook.attachments[$scope.signatureBook.rightSelectedThumbnail].status = 'SIGN';
+        } else {
+          alert(response.data.error);
+        }
+
+      }, function errorCallback(error) {
+      });
+
     }
-  };
 
+    vm.unsignFile = function(attachment) {
+      var collId;
+      var resId;
+      if (attachment.res_id == 0) {
+        resId = attachment.res_id_version;
+        collId = "res_version_attachments";
+      } else if (attachment.res_id_version == 0) {
+        resId = attachment.res_id;
+        collId = "res_attachments";
+      }
+
+      $http({
+        method : 'PUT',
+        url    : globalConfig.coreurl + 'rest/' + collId + '/' + resId + '/unsign',
+        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+      }).then(function successCallback(response) {
+
+        if (response.data.status == "OK") {
+          $scope.signatureBook.rightViewerLink = "index.php?display=true&module=visa&page=view_pdf_attachement&res_id_master=" + vm.resId + "&id=" + resId;
+          $scope.signatureBook.attachments[$scope.signatureBook.rightSelectedThumbnail].viewerLink = $scope.signatureBook.rightViewerLink;
+          $scope.signatureBook.attachments[$scope.signatureBook.rightSelectedThumbnail].status = 'A_TRA';
+        } else {
+          alert(response.data.error);
+        }
+
+      }, function errorCallback(error) {
+      });
+
+    };
+
+    $scope.backToBasket = function() {
+      location.hash = "";
+      location.reload();
+    };
+
+    $scope.changeLocation = function(resId) {
+      $location.path(vm.basketId + "/signatureBook/" + resId);
+    };
+
+    $scope.validForm = function() {
+      if ($j("#signatureBookActions option:selected")[0].value != "") {
+        //$interval.cancel(intervalPromise);
+        unlockDocument($routeParams.resId);
+
+        valid_action_form(
+          'empty',
+          'http://127.0.0.1/maarch_trunk_git/apps/maarch_entreprise/index.php?display=true&page=manage_action&module=core',
+          $scope.signatureBook.currentAction,
+          $routeParams.resId,
+          'res_letterbox',
+          'null',
+          'letterbox_coll',
+          'page',
+          false,
+          [$j("#signatureBookActions option:selected")[0].value]
+        );
+      }
+    };
 
 
-  //Initialize View
-  vm.basketId = $routeParams.basketId;
-  vm.resId = $routeParams.resId;
+    //Initialize View
+    vm.basketId = $routeParams.basketId;
+    vm.resId = $routeParams.resId;
 
-  getDatas($routeParams.basketId, $routeParams.resId);
+    getDatas($routeParams.basketId, $routeParams.resId);
 
-  lockDocument($routeParams.resId);
-  $interval(function () {
     lockDocument($routeParams.resId);
-  }, 50000);
+    $interval(function () {
+      lockDocument($routeParams.resId);
+    }, 50000);
 
 }]);
\ No newline at end of file
diff --git a/modules/visa/sign_file.php b/modules/visa/sign_file.php
index 5612c84d96f..a7982cd0f1a 100644
--- a/modules/visa/sign_file.php
+++ b/modules/visa/sign_file.php
@@ -23,7 +23,7 @@ $core_tools->load_lang();
 
 if (!isset($_SESSION['user']['pathToSignature']) ||$_SESSION['user']['pathToSignature'] == '') {
     $_SESSION['error'] = _IMG_SIGN_MISSING;
-	echo "{status:1, error : '". _IMG_SIGN_MISSING ."'}";
+	echo "{\"status\":1, \"error\" : \"". _IMG_SIGN_MISSING ."\"}";
 	exit;
 }
 
@@ -47,7 +47,7 @@ if (!empty($_REQUEST['id']) && !empty($_REQUEST['collId'])){
     }
 	
     if ($stmt->rowCount() < 1) {
-    	echo "{status:1, error : '". _FILE . ' ' . _UNKNOWN ."'}";
+    	echo "{\"status\":1, \"error\" : \"". _FILE . ' ' . _UNKNOWN ."\"}";
 		exit;
 		//$_SESSION['error'] = _FILE . ' ' . _UNKNOWN;
     } 
@@ -63,7 +63,7 @@ if (!empty($_REQUEST['id']) && !empty($_REQUEST['collId'])){
 		
 		//java -jar C:\Temp\SigniText.jar C:\Temp\blowagie\Modele.pdf C:\Temp\blowagie\extracted\images\Modele-1.jpg 140 114 C:\Temp\blowagie\images
 		if (!file_exists($fileOnDs)){
-			echo "{status:1, error : 'Fichier $fileOnDs non present'}";
+			echo "{\"status\":1, \"error\" : \"Fichier $fileOnDs non present\"}";
 			exit;
 		}
 		$cmd = "java -jar " 
@@ -82,12 +82,10 @@ if (!empty($_REQUEST['id']) && !empty($_REQUEST['collId'])){
 		
 		include 'modules/visa/save_attach_res_from_cm.php';
 		
-		echo "{status:0, new_id : $id}";
+		echo "{\"status\": 0, \"new_id\": $id}";
 		exit;
 	}
 } else {
 	$_SESSION['error'] = _ATTACHMENT_ID_AND_COLL_ID_REQUIRED;
 }
 exit;
-
-?>
diff --git a/rest/index.php b/rest/index.php
index a722e4f11d4..627d78908f3 100644
--- a/rest/index.php
+++ b/rest/index.php
@@ -103,5 +103,6 @@ $app->put('/status', \Core\Controllers\StatusController::class . ':update');
 $app->delete('/status/{id}', \Core\Controllers\StatusController::class . ':delete');
 
 $app->get('/{basketId}/signatureBook/{resId}', \Visa\Controllers\VisaController::class . ':getSignatureBook');
+$app->put('/{collId}/{resId}/unsign', \Visa\Controllers\VisaController::class . ':unsignFile');
 
 $app->run();
-- 
GitLab