...
Dans "Admin" puis "Circuit", vous avez accès à l'outil permet de consulter et de modifier les circuits de signatures. Il est possible de filtrer les circuits présents dans esup-signature :
- Workflows globaux (définis pas les administrateurs)
- Classes workflow (définies par les développeurs)
- Workflows Utilisateurs (définis pas les utilisateurs)
Création d'un circuit classique
...
Dans cette vue il est possible d'activer la notion d'étape "infinie" en activant "L'utilisateur peut ajouter une étape avant la suivante". Si cette option est activée, vous donnerez la possibilité aux participants de cette étape, d'ajouter des étapes intermédiaires (non prévues à l'origine).
Chaque étape créée à la suite d'une étape "infinie" sera elle aussi une étape "infini". Cela se traduit, au niveau de l'interface utilisateur, par une question qui lui est posée lorsque qu'il s’apprête à signer : "Signer et passer à l'étape suivante" ou "signer et ajouter une étape"
Paramètres généraux d'un circuit
...
- Titre
- Description (celle qui apparaît sur le le bouton permettant de démarrer le circuit)
- Visibilité publique (tout le monde peut démarrer le circuit)
- Avertir tous les participants à la fin du circuit
- Les rôles autorisés à démarrer le circuit. Les rôles sont obtenus en fonction de la configuration voir Configuration de la sécurité. Ce paramètre est surchargé par la "Visibilité publique"
- Les gestionnaires du circuits ; ils peuvent accéder à toutes les demandes correspondant au circuit
- Type de délégations autorisées : Lecture, création et/ou signature. Verrouille les possibilités de déléguer, à d'autres, les actions sur les documents correspondant à ce circuit
- Protocole pour la source des documents parmi : smb (partage réseau, cmis (GES nuxeo, alfresco, ...), vfs (dossier local)
- Lien pour la source des documents
- Le paramètre "Scanner les métadonnées" (valable seulement pour les documents provenant d'une source de donnée)
- Une ou plusieurs destinations parmi : smb, cmis, vfs ou mail
Définir une source pour les documents
Comme vu précédemment, il est possible de définir un emplacement source pour alimenter un circuit. Esup signature possède une tache planifiée qui "scan" régulièrement toutes les sources de documents définies dans les différents circuits.
Pour cela il faut, en premier lieu, avoir défini un compte d'accès pour chaque types de source que l'on voudra utiliser. Esup-signature propose nativement les protocoles SMB, CMIS et VFS repris de l'application esup-filemanager (Esup File Manager).
La configuration globale se fait ici : Sources et configuration.
Pour chaque circuit on peur saisir un lien vers lequel le compte configuré à un accès complet, par exemple : smb://<host>/bdc/a_signer par exemple.
Voici la liste des protocoles pris en charge par esup-signature:
- smb://
- cmis://
- file://
- sftp://
- ftp://
| Remarque |
|---|
Lorsqu'un document est intégré de cette façon, il est supprimé du dossier source. |
Définir une destination pour les documents
Il est possible de définir un/des emplacement(s) de destination pour stocker les documents en fin de circuit.
La configuration globale doit être faite comme vu pour les sources de documents.
Pour chaque circuit on peut saisir un ou plusieurs lien vers lesquels les documents seront envoyés.
Voici la liste des protocoles pris en charge par esup-signature pour les destinations:
- smb://
- cmis://
- file://
- sftp://
- ftp://
- http:// https:// (fera une requete vers l'url avec en parametre l(ID de la demande de siganture et son statut signé ou refusé)
- mailto (envoi du document en pièce jointe au destinataire)
Définir les participants à un circuit à posteriori
Dans certains cas, les participants à une étape ne peuvent pas être prédéfinis dans esup-signature.
Dans le cas concret du circuit des bons de commande à l'université de Rouen, les signataires sont détermines en fonction de l'unité budgétaire (donc par l'application métier, SIFAC).
Le cas d'usage à Rouen est que les utilisateurs génèrent des bons de commande "à signer" au format PDF et les déposent dans leur dossier de travail.
Trois possibilités :
Mettre en place un script qui va calculer le workflow, utiliser les web-services pour configurer les participants du circuit des bons de commandes et injecter le document. (pour plus de détails sur les web services voir : Web services REST)
Mettre en place un script qui calcule le workflow, inscrit ce workflow dans les métas-données du document (PDF) et le copie dans un dossier défini comme "source" au niveau d'Esup-signature.
Pour cette dernière solution il faut donc créer un circuit comme vu précédemment, cocher la case "Scanner les métadonnées des PDF" et définir une source pour la récupération des documents au niveau des paramètres générauxImplémenter une classe worflow (voir le chapitre suivant)
Les métas-données qui doivent être inscrite dans les document PDF sont les suivantes:
- sign_type_default_val : contenant le type de signature (visa, pdfImageStamp, certSign ou nexuSign)
- sign_step#<n> : contenant la liste des participants de l'étape n
- sign_target : contenant le chemin de dépôt des documents après signature
Lorsque le scheduler passera pour importer les documents, ceux-ci seront analysés, le circuit sera généré en fonction des informations trouvées dans les métas-données.
Exemple de code java permettant d'ajouter les métas-données à un fichier pdf :
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
PDDocument document = PDDocument.load(in);
PDDocumentInformation info = document.getDocumentInformation();
info.setCustomMetadataValue("sign_type_default_val", "pdfImageStamp");
info.setCustomMetadataValue("sign_step#1", "[machin@univ-ville.fr, truc@univ-ville.fr]");
info.setCustomMetadataValue("sign_target_key","smb://serveur_de_fichiers/la_destination/signed");
|
Utiliser les classes workflow
Pour les cas les plus spécifiques, il est possible d'ajouter, au code source original d'esup-signature, une classe qui décrira précisément un circuit.
| Info |
|---|
L’intérêt de cette solution est de pouvoir mettre en place des mécanismes complexes de calcul des participants. On peut par exemple imaginer de calculer le n+1 de l'utilisateur courant. Cela nécessite d'avoir des compétences en développement et connaitre notamment le langage Java et le framework Spring |
Une nouvelle classe workflow devra implémenter la classe "DefaultWorkflow". Des exemples sont déjà présents dans le code source original d'Esup-signature dans le dossier src/main/java/org/esupportail/esupsignature/service/workflow/impl/
Votre classe doit être construite comme suit :
...
Detection des emplacements de signature
(depuis la version 1.30.x)
Par défaut, esup-signature détecte automatiquement les champs signatures vierges des PDF. Ceux-ci sont attribués dans l'ordre de lecture (page, hauteur de haut en bas, largeur de gauche à droite). Dans le cas où le doccument, ne contient pas de champs signature mais dans lequel se trouve des éléments permettant de determiner l'emplacement des signatures, il est possible, de configurer une detection.
Pour cela, il faut que les éléments soit facilement discriminables et qu'ils soient tous du même type. De la même manière, il seront détéctés dans l'ordre de lecture, il faut donc que le document soit correctement construit.
Pour configurer cette détection, il faut utiliser le paramètre "Pattern de détéction des champs signature".
Le modèle est construit à l’aide du type d’élément entre crochets suivi d’une regex pour le nom de l’attribut. Voici la liste des éléments pris en charge :
- [TextField]
- [PushButton]
- [AnnotationLink]
Exemples :
[TextField]^(SIG_SIGNATURE|SIG_CACHET)$ : pour détecter les champs de type TextField avec les attributs "nom" ("name") SIG_SIGNATURE ou SIG_CACHET
ou
[AnnotationLink]^signature : pour détecter les champs de type AnnotationLink commençant par "signature"
Définir une source pour les documents
Comme vu précédemment, il est possible de définir un emplacement source pour alimenter un circuit. Esup signature possède une tache planifiée qui "scan" régulièrement toutes les sources de documents définies dans les différents circuits.
Pour cela il faut, en premier lieu, avoir défini un compte d'accès pour chaque types de source que l'on voudra utiliser. Esup-signature propose nativement les protocoles SMB, CMIS et VFS repris de l'application esup-filemanager (Esup File Manager).
La configuration globale se fait ici : Configuration#fs(filesystem).
Pour chaque circuit on peur saisir un lien vers lequel le compte configuré à un accès complet, par exemple : smb://<host>/bdc/a_signer par exemple.
Voici la liste des protocoles pris en charge par esup-signature ainsi que la manière de les configurer :
| Type | Chemin | Adresse à saisir | Remarques |
|---|---|---|---|
| SMB | Absolu | smb://<adresse du serveur>/<chemin du dossier> | smb-test-uri à configurer obligatoirement pour activer la fonctionnalité. L'utilisateur est configuré dans application.yml (smb-login, smb-password) |
| CMIS | Relatif | cmis://<chemin du dossier> ex: cmis://default-domain/workspaces/test | Il faut configurer l'adresse du server nuxeo dans application.yml au niveau de (cmis-test-uri ex: http://mon-nuxeo.univ-ville.fr:8081/nuxeo, cmis-login, cmis-password) Cette partie est amenée à changer prochainement pour permettre les chemins absolus |
| FILE | Absolu | /<chemin du dossier> | vfs-test-urià configurer obligatoirement pour activer la fonctionnalité. |
| FTP / SFTP | Absolu | ftp://<user>:<password>@<adresse du serveur>/<chemin du dossier> | vfs-test-uri à configurer obligatoirement pour activer la fonctionnalité. |
| Remarque |
|---|
Lorsqu'un document est intégré de cette façon, il est supprimé du dossier source. |
...
Définir une destination pour les documents
Il est possible de définir un/des emplacement(s) de destination pour stocker les documents en fin de circuit.
La configuration globale doit être faite comme vu pour les sources de documents.
Pour chaque circuit on peut saisir un ou plusieurs lien vers lesquels les documents seront envoyés.
Voici la liste des protocoles pris en charge par esup-signature pour les destinations:
- smb, cmis, file, sftp / ftp, pour ces types, il faut suive les explications du tableau ci-dessus
- http:// https:// (fera une requete, type REST, vers l'url avec en paramètre l'ID de la demande de signature, son statut signé ou refusé et le numéro d'étape concerné
- mailto (envoi du document en pièce jointe au destinataire)
Verification du bon fonctionnement de la copie coté serveur avec smbclient :
| Bloc de code | ||
|---|---|---|
| ||
apt install smbclient
echo "Contenu de test" > /tmp/test_fichier.txt
smbclient //stockage.univ-ville.fr/nom_du_partage -W MON_WORKGROUP -U mon_uid -c "put /tmp/test_fichier.txt mon_du_dossier/test_fichier.txt" |
La copie doit fonctionner pour qu'esup-signature puisse en faire autant.
Transmission vers REST
(maj depuis pour la version 1.30.x)
Lorsque vous configurez une adresse type "rest", donc vers un websevice en écoute, esup-signature va tenté de transmettre les données via GET et via POST.
Voici à quels moments les requettes sont transmisent :
- À chaque signature / refus (avec le motif)
- À la suppression / suppression définitive
- À la restautation
- À la fin du circuit
Les statuts transmis sont les suivants : pending, refused, deleted, restored, cleaned, completed
Voici la liste (et le format) des données transmisent :
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
{
"signRequestId": "XXXX",
"status": "pending",
"step": "1",
"userEppn": "toto@univ-ville.fr",
"comment": ""
}
|
En GET les informations sont transmisent via les paramètres de la requête.
Voici un exemple de code à mettre coté application métier pour permettre à esup-signature de transmettre les informations du circuit via REST. Ici il s'agir d'un controleur Spring. La requête est envoyée en GET, esup-signature doit avoir un accès direct à cette API.
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
@GetMapping(value = "/return-test")
@ResponseBody
public ResponseEntity<Void> returnTest(@RequestParam("signRequestId") String signRequestId, @RequestParam("status") String status, @RequestParam("step") String step) {
logger.info(signRequestId + ", " + status + ", " + step);
//ici, le code à executer coté application métier en fonction du statut
return ResponseEntity.ok().build();
}
|
En POST, les informations sont transmisent sous forme de map json:
Voici un script python permettant de simuler un mini server web et affichant les données reçues ici en http://localhost:8000 :
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import json
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Parse l'URL et ses paramètres
parsed_path = urlparse(self.path)
query_components = parse_qs(parsed_path.query)
# Affiche les informations dans la console
print("Received GET request:")
print(f"Path: {self.path}")
print("Query parameters:")
for key, values in query_components.items():
for value in values:
print(f" {key}: {value}")
# Prépare la réponse (simple pour le client)
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b"<html><body><h1>GET request received</h1></body></html>")
def do_POST(self):
# Récupère la longueur des données envoyées
content_length = int(self.headers['Content-Length'])
# Récupère le corps de la requête
post_data = self.rfile.read(content_length)
# Tente de décoder les données comme JSON
try:
json_data = json.loads(post_data)
print("Received POST request with JSON data:")
print(json.dumps(json_data, indent=4))
except json.JSONDecodeError:
print("Received POST request with invalid JSON")
json_data = {"error": "Invalid JSON"}
# Prépare la réponse (simple pour le client)
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b"<html><body><h1>POST request received</h1></body></html>")
def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f"Starting server on port {port}...")
httpd.serve_forever()
if __name__ == '__main__':
run()
|
| Avertissement |
|---|
Attention à partir du moment où une destination REST à été mise en place, il faut qu'il y ai au moins un web service GET ou POST qui répond sinon les créations, signature, et refus ne pourront pas fonctionner. |
...
Définir les participants à un circuit à posteriori
Dans certains cas, les participants à une étape ne peuvent pas être prédéfinis dans esup-signature.
Dans le cas concret du circuit des bons de commande à l'université de Rouen, les signataires sont détermines en fonction de l'unité budgétaire (donc par l'application métier, SIFAC).
Le cas d'usage à Rouen est que les utilisateurs génèrent des bons de commande "à signer" au format PDF et les déposent dans leur dossier de travail.
Trois possibilités :
Mettre en place un script qui va calculer le workflow, utiliser les web-services pour configurer les participants du circuit des bons de commandes et injecter le document. (pour plus de détails sur les web services voir : Web services REST)
Mettre en place un script qui calcule le workflow, inscrit ce workflow dans les métas-données du document (PDF) et le copie dans un dossier défini comme "source" au niveau d'Esup-signature.
Pour cette dernière solution il faut donc créer un circuit comme vu précédemment, cocher la case "Scanner les métadonnées des PDF" et définir une source pour la récupération des documents au niveau des paramètres générauxImplémenter une classe worflow (voir le chapitre suivant)
Les métas-données qui doivent être inscrite dans les document PDF sont les suivantes:
- sign_type_default_val : contenant le type de signature (visa, pdfImageStamp, certSign ou nexuSign)
- sign_step#<n> : contenant la liste des participants de l'étape n
- sign_target : contenant le chemin de dépôt des documents après signature
Lorsque le scheduler passera pour importer les documents, ceux-ci seront analysés, le circuit sera généré en fonction des informations trouvées dans les métas-données.
Exemple de code java permettant d'ajouter les métas-données à un fichier pdf :
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
PDDocument document = PDDocument.load(in);
PDDocumentInformation info = document.getDocumentInformation();
info.setCustomMetadataValue("sign_type_default_val", "pdfImageStamp");
info.setCustomMetadataValue("sign_step#1", "[machin@univ-ville.fr, truc@univ-ville.fr]");
info.setCustomMetadataValue("sign_target_key","smb://serveur_de_fichiers/la_destination/signed");
|
...
Utiliser les classes workflow
Pour les cas les plus spécifiques, il est possible d'ajouter, au code source original d'esup-signature, une classe qui décrira précisément un circuit.
| Info |
|---|
L’intérêt de cette solution est de pouvoir mettre en place des mécanismes complexes de calcul des participants. On peut par exemple imaginer de calculer le n+1 de l'utilisateur courant. Cela nécessite d'avoir des compétences en développement et connaitre notamment le langage Java et le framework Spring |
Une nouvelle classe workflow devra implémenter la classe "DefaultWorkflow". Des exemples sont déjà présents dans le code source original d'Esup-signature dans le dossier src/main/java/org/esupportail/esupsignature/service/workflow/impl/
Votre classe doit être construite comme suit :
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
package org.esupportail.esupsignature.service.workflow.impl;
import org.esupportail.esupsignature.entity.Data;
import org.esupportail.esupsignature.entity.User;
import org.esupportail.esupsignature.entity.WorkflowStep;
import org.esupportail.esupsignature.entity.enums.SignType;
import org.esupportail.esupsignature.exception.EsupSignatureUserException;
import org.esupportail.esupsignature.service.workflow.DefaultWorkflow;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class BasicWorkflow extends DefaultWorkflow {
private String name = "BasicWorkflow";
private String description = "Une signature";
private List<WorkflowStep> workflowSteps;
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public List<WorkflowStep> getWorkflowSteps() {
if(this.workflowSteps == null) {
try {
this.workflowSteps = generateWorkflowSteps(userService.getCurrentUser(), null, null);
} catch (EsupSignatureUserException e) {
return null;
}
}
return this.workflowSteps;
}
public void initWorkflowSteps | ||||
| Bloc de code | ||||
| ||||
package org.esupportail.esupsignature.service.workflow.impl; import org.esupportail.esupsignature.entity.Data; import org.esupportail.esupsignature.entity.User; import org.esupportail.esupsignature.entity.WorkflowStep; import org.esupportail.esupsignature.entity.enums.SignType; import org.esupportail.esupsignature.exception.EsupSignatureUserException; import org.esupportail.esupsignature.service.workflow.DefaultWorkflow; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component public class BasicWorkflow extends DefaultWorkflow { private String name = "BasicWorkflow"; private String description = "Une signature"; private List<WorkflowStep> workflowSteps; @Override public String getName() { return name; } @Override public String getDescription() { return description; } @Override public List<WorkflowStep> getWorkflowSteps() { if(this.workflowSteps == null) {workflowSteps = new ArrayList<>(); } @Override public List<WorkflowStep> try { generateWorkflowSteps(User user, Data data, List<String> recipentEmailsStep) throws EsupSignatureUserException { List<WorkflowStep> this.workflowSteps = generateWorkflowSteps(userService.getCurrentUser(), null, null); } catch (EsupSignatureUserException e) { new ArrayList<>(); /* ici on construit la liste des étapes du circuit */ WorkflowStep workflowStep = returnnew nullWorkflowStep(); }workflowStep.setStepNumber(1); }workflowStep.setSignType(SignType.pdfImageStamp); return this.workflowStepsworkflowStep.setDescription("Choix du signataire"); } public void initWorkflowSteps() {workflowStep.setChangeable(true); this.workflowSteps if(data != new ArrayList<>(); null) { } @Override public List<WorkflowStep> generateWorkflowSteps(User user, Data data, List<String> recipentEmailsStep) throws EsupSignatureUserExceptionworkflowStep.setRecipients(workflowService.getFavoriteRecipientEmail(1, data.getForm(), recipentEmailsStep, user)); } else { List<WorkflowStep> workflowSteps = new ArrayList<>(); /* ici on construit la liste des étapes du circuit */ WorkflowStep workflowStep = new WorkflowStep(); workflowStep.setStepNumber(1); workflowStep.setSignType(SignType.pdfImageStamp); workflowStep.setDescription("Choix du signataire"); workflowStep.setChangeable(true); if(data != null) { workflowStep.setRecipients(workflowService.getFavoriteRecipientEmail(1, data.getForm(), recipentEmailsStep, user)); } else { workflowStep.getRecipients().add(recipientService.createRecipient(null, userService.getGenericUser("Utilisateur issue des favoris", ""))); } workflowSteps.add(workflowStep); return workflowSteps; } } |
Il faut donc a minima :
...
workflowStep.getRecipients().add(recipientService.createRecipient(null, userService.getGenericUser("Utilisateur issue des favoris", "")));
}
workflowSteps.add(workflowStep);
return workflowSteps;
}
}
|
Il faut donc a minima :
- Préciser un nom et une description (dans name et description)
- Implémenter la fonction generateWorkflowSteps() qui retournera une liste de WorkFlowStep (les étapes calculée en fonction de l'utilisateur courant "user")
...
Récupérer les données via Power Query
Tout d'abord il faut se générer une clé API depuis les paramètres utilisateurs. Si le champ est vide il suffit de cliquer sur actualiser.
| Remarque |
|---|
Attention, si une clé est déjà présente, cela invalidera la précédente et déconnectera les sources existant. Attention aussi, les données présentes dans le fichier excel ne pourrons plus être mises à jour mais resterons présentes tout de même ! Il faudra donc bien protéger le fichier. Un nouveau paramètre csv-access-authorize-mask permet de limiter l'usage de la synchro csv pour un réseau précis. |
Via Excel, Ouvrir Power Query ("Obtebir des données → Power Query)
Dans Power Query, "Nouvelle source" → "Autres sources" → "Requête vide"
Clique droit sur la requête (Requête1 par ex) → "Éditeur avancé" et copier un script du type :
| Bloc de code | ||||
|---|---|---|---|---|
| ||||
let
Source = Csv.Document(Web.Contents("https://esup-signature.univ-ville.fr/ws/workflows/XXXXXXX/datas/csv", [ApiKeyName="XApiKey"]), [Delimiter=",", Columns=24, Encoding=65001, QuoteStyle=QuoteStyle.None]),
Table_table = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
in
Table_table |
Il faut, ensuite, cliquer sur "Modifier les informations d'identification", choisir API Web et coller votre clé.
...
