Automatiser vos processus avec bxFlow
La référence des points d’extension du moteur : bibliothèque lib, comportements, conditions, post-fonctions et appel de connecteurs.
§Introduction
Le moteur workflow de bxFlow est configurable sans déployer de code : un administrateur de processus définit les étapes, les écrans et les transitions depuis l’interface. Pour tout ce qui ne se règle pas par configuration — calculs, validations, règles conditionnelles, effets de bord — il branche de petits scripts JavaScript.
Ces scripts s’exécutent dans le navigateur, dans un bac à sable. Ils supportent async/await. Il existe trois contextes, chacun avec ses déclencheurs, son jeu de fonctions injectées et sa sémantique propre. C’est le point le plus important à comprendre avant d’écrire : le même nom de fonction n’existe pas partout, et écrire un champ ne se fait pas de la même façon selon le contexte.
| Contexte | Où on l’écrit | Déclencheurs | Ce qu’il décide |
|---|---|---|---|
| Comportements | Éditeur de comportements d’un écran | on_form_load, on_field_change, on_case_load | L’état vivant du formulaire : valeurs, visibilité, obligation, validation, boutons de transition. |
| Conditions | Champ « condition » d’une transition | À l’affichage du dossier | La visibilité d’un bouton de transition (return false = masqué). |
| Post-fonctions | Onglet « Post-fonctions » d’une transition | Franchissement d’une transition | Les effets de bord une fois la transition franchie : assignation, MAJ, connecteurs, e-mails. |
Cycle de vie des déclencheurs
on_form_load— s’exécute une fois, à l’ouverture du formulaire. Idéal pour préparer l’état initial (masquer des champs, poser des valeurs par défaut).on_field_change— s’exécute à chaque modification d’un champ. On peut le restreindre à un champ précis (paramètre « champ déclencheur ») pour ne pas recalculer à chaque frappe ailleurs.on_case_load— s’exécute à l’ouverture du dossier. C’est le seul endroit où l’on peut afficher / masquer des boutons de transition (showTransition/hideTransition).
Adresser un champ
Partout, un champ est désigné par sa clé (son field key, ex. date_debut), pas par son libellé. getValue('date_debut') lit sa valeur courante ; les fonctions d’écriture prennent la même clé en premier argument.
getValue('vehicule') renvoie l’objet complet (pas l’UUID) — vous pouvez lire getValue('vehicule').marque directement. À l’écriture, vous pouvez passer indifféremment l’objet ou son identifiant.Gestion des erreurs (par contexte)
- Comportements — une erreur dans un script n’interrompt pas le formulaire : le reste de l’écran continue de fonctionner. Pour la mise au point, utilisez
loget le mode Test de l’éditeur. - Conditions — en cas d’erreur, le bouton reste affiché : testez vos conditions. Une condition règle l’affichage d’un bouton, pas un droit — pour réserver une transition à certains profils, utilisez le rôle requis sur la transition.
- Post-fonctions — best-effort : une erreur sur une action (ex. e-mail) n’annule pas la transition déjà franchie.
§Environnement d’exécution
Vos scripts s’exécutent comme des fonctions asynchrones : vous pouvez utiliser await directement. Voici ce dont ils disposent.
Ce qui est disponible
- Les fonctions injectées du contexte (voir chaque section).
- La bibliothèque
lib. - Les objets standard du langage :
JSON,Math,Number,String,Array,Object,Date, les littéraux de gabarit,await…
Ce qui ne l’est pas
Un script n’a accès ni au réseau, ni à la page, ni au stockage du navigateur, et ne peut pas programmer d’exécution différée. Il n’importe aucune dépendance externe.
callConnector, getIssuesByTemplate, updateIssue…). Elles s’exécutent avec les droits de l’utilisateur connecté : une action qu’il n’est pas autorisé à faire dans l’interface lui sera également refusée depuis un script.§Bibliothèque lib
lib est une bibliothèque utilitaire maison, disponible dans les trois contextes sous le nom lib. require('lib') renvoie le même objet mais n’est injecté que dans les comportements : l’appeler depuis une post-fonction ou une condition lève require is not defined. Elle est 100 % pure et tolérante : sur une entrée invalide, elle renvoie une valeur neutre ('', null, 0) plutôt que de lever une exception. Vous pouvez donc l’appeler sans multiplier les try/catch.
Dates
Formats reconnus en entrée (par parseDate, utilisé partout) : ISO YYYY-MM-DD, ISO avec heure, YYYYMMDD (8 chiffres), et les formats FR DD/MM/YYYY, DD-MM-YYYY, DD.MM.YYYY. En sortie, toute fonction qui renvoie une date renvoie une string ISO YYYY-MM-DD.
parseDate(date)→ Date | nullConvertit une valeur en objet Date locale en essayant tous les formats ci-dessus, puis en dernier recours new Date(). Renvoie null si rien n’est interprétable. Sert de base à toutes les autres fonctions de date.
formatDate(date, fmt?)→ stringFormate une date. fmt vaut 'iso' (défaut, 2026-07-06) ou 'fr' (06/07/2026). Renvoie '' si la date est invalide.
lib.formatDate('2026-07-06', 'fr') // → '06/07/2026'
lib.formatDate('n’importe quoi') // → ''today()→ stringLa date du jour au format ISO. Pratique pour horodater ou calculer une échéance.
addDays(date, n) · subDays(date, n)→ stringAjoute (ou retire) n jours calendaires. n peut être négatif. Renvoie une date ISO, ou '' si l’entrée est invalide.
lib.addDays(lib.today(), 30) // échéance à J+30
lib.subDays('2026-07-06', 7) // → '2026-06-29'addMonths(date, n)→ stringAjoute (ou retire, si n < 0) n mois entiers.
diffDays(a, b)→ number | nullNombre de jours calendaires entre a et b, soit b − a, calculé de minuit à minuit (aucun effet d’heure ni de fuseau). Négatif si b est avant a. null si une date est invalide.
diffMonths(a, b)→ number | nullNombre de mois entiers entre les deux dates.
businessDays(start, end)→ number | nullNombre de jours ouvrés (lundi→vendredi) entre deux dates, bornes incluses. Exclut uniquement les week-ends — pas les jours fériés.
lib.businessDays('2026-07-06', '2026-07-10') // → 5 (lun→ven)
lib.businessDays('2026-07-04', '2026-07-06') // → 1 (sam+dim exclus, lun inclus)lib.businessDays(a,b) - nbFeriesDansIntervalle.daysBetween(start, end)→ number | nullJours calendaires bornes incluses — équivaut à diffDays + 1. daysBetween('2026-07-01','2026-07-01') vaut 1.
isBefore(a, b) · isAfter(a, b) · isSameDay(a, b)→ booleanComparaisons au jour (l’heure est ignorée).
isWeekend(date) · dayOfWeek(date)→ boolean · numberisWeekend : samedi ou dimanche. dayOfWeek : 0 = dimanche … 6 = samedi (-1 si invalide).
startOfMonth(date) · endOfMonth(date)→ stringPremier / dernier jour du mois de la date donnée, en ISO.
Collections
Fonctions façon lodash. Le paramètre key (l’« itératee ») accepte soit une string (nom de propriété : 'montant'), soit une fonction (x => x.montant). Toutes sont non-mutantes (elles renvoient une copie).
groupBy(arr, key)→ Record<string, T[]>Regroupe les éléments dans un objet, indexé par la valeur de key. Chaque entrée est un tableau.
lib.groupBy(lignes, l => l.categorie)
// → { transport: [...], repas: [...] }keyBy(arr, key)→ Record<string, T>Indexe par clé, en gardant un seul élément par valeur (le dernier rencontré gagne).
countBy(arr, key)→ Record<string, number>Compte le nombre d’éléments par valeur de clé.
sortBy(arr, key)→ T[]Trie par ordre ascendant selon la clé (copie, non mutant).
uniq(arr) · uniqBy(arr, key)→ T[]Déduplique : uniq par identité (via Set), uniqBy par valeur de clé.
sumBy(arr, key) · meanBy(arr, key)→ numberSomme / moyenne des valeurs. Les valeurs non numériques comptent pour 0 ; meanBy d’un tableau vide vaut 0.
lib.sumBy(lignes, 'montant') // total des montants
maxBy(arr, key) · minBy(arr, key)→ T | nullRenvoie l’élément (pas la valeur) au maximum / minimum de la clé, ou null si le tableau est vide.
chunk(arr, size)→ T[][]Découpe le tableau en paquets de longueur size (≥ 1).
range(start, end?, step?)→ number[]Génère une suite numérique. range(3) → [0,1,2] ; range(2, 6) → [2,3,4,5] ; step négatif supporté.
get(obj, path, def?)→ unknownAccès sûr à une valeur imbriquée par chemin, sans planter si un maillon est absent. Supporte la notation pointée et les index (a.b.0.c ou a.b[0].c). Renvoie def si le chemin n’existe pas.
lib.get(resultatConnecteur, 'body.items.0.id', null)
Format & nombres (locale par défaut fr-FR)
currency(n, code?, locale?)→ stringFormate un montant en devise. code par défaut 'EUR'. lib.currency(1234.5) → 1 234,50 €.
number(n, decimals?, locale?)→ stringFormate un nombre avec séparateurs de milliers. lib.number(1234.5, 2) → 1 234,50.
round(n, decimals?)→ numberArrondit à decimals décimales (défaut 0). lib.round(3.14159, 2) → 3.14.
clamp(n, min, max)→ numberBorne n entre min et max. lib.clamp(150, 0, 100) → 100.
percent(n, decimals?, locale?)→ stringFormate une proportion en pourcentage. lib.percent(0.75) → 75 %.
capitalize(s) · truncate(s, len, suffix?) · slugify(s)→ stringcapitalize('bonjour') → Bonjour · truncate('abcdef', 4) → abc… · slugify('Été à Paris!') → ete-a-paris.
Validation
isEmail(s)→ booleanVrai si la string ressemble à une adresse e-mail (contrôle de forme basique, pas de vérification d’existence).
isPhone(s)→ booleanVrai pour un numéro de 6 à 30 caractères composé de +, chiffres et séparateurs usuels.
isEmpty(v)→ booleanVrai pour null, '', '[]', un tableau vide, ou un objet vide. Pratique pour tester un champ « rempli ».
isNumeric(v)→ booleanVrai si v est un nombre ou une string entièrement numérique.
§Comportements — API
Le contexte le plus riche : il pilote l’état vivant du formulaire. Toutes les fonctions ci-dessous y sont disponibles. Celles marquées async renvoient une Promise — utilisez await.
Lire & écrire un champ
getValue(key)→ unknownValeur courante du champ key. Renvoie '' si le champ est absent. Pour un champ Objet métier, renvoie l’objet complet (pré-résolu).
setValue(key, value)→ voidFixe la valeur d’un champ. Un objet BO est normalisé en UUID ; un tableau d’objets BO en tableau d’UUID. Déclenche la logique de dépendance du formulaire.
keystringClé du champ cible.valueunknownNouvelle valeur (string, number, tableau, objet BO…).Afficher, masquer, rendre obligatoire
show(key) · hide(key)→ voidAffiche ou masque un champ. Un champ masqué n’est pas soumis.
setRequired(key, required)→ voidRend un champ obligatoire (true) ou optionnel (false). Un champ obligatoire vide bloque la soumission.
disable(key) · enable(key)→ voiddisable grise le champ (visible mais non éditable) ; enable le réactive. À distinguer de hide (qui le retire).
Validation & messages
setError(key, message)→ voidMarque le champ en erreur et affiche message. Bloque la soumission tant que l’erreur n’est pas levée. À rappeler à chaque évaluation (voir clearError).
clearError(key)→ voidLève l’erreur posée sur le champ. Bonne pratique : dans un on_field_change, appeler clearError dans la branche « valide » pour ne pas laisser une erreur obsolète.
setMessage(key, message) · clearMessage(key)→ voidMessage informatif (non bloquant) sous le champ — pour une aide contextuelle ou un total calculé. clearMessage l’efface.
Boutons de transition (contexte on_case_load)
showTransition(nameOrId) · hideTransition(nameOrId)→ voidAffiche / masque un bouton de transition, par nom ou par ID. N’a de sens que dans on_case_load. Pour une règle purement déclarative, préférez une condition de transition (section suivante).
// Masquer "Valider" sauf pour le rôle Manager
const me = getCurrentUser()
if (!me || !me.wf_role_names.includes('Manager')) hideTransition('Valider')Assignation & annuaire
getAssignee()→ { type, id, name } | nullL’assigné courant du dossier, en tenant compte d’une assignation posée plus tôt dans le même run. type vaut 'user' ou 'role'. null si non assigné.
setAssignee(userId | null)→ voidAssigne le dossier à un utilisateur par son UUID. null désassigne.
setAssigneeByRole(slugOrName)→ voidAssigne à un rôle workflow (par slug ou nom), plutôt qu’à une personne précise.
getRoles()→ BehaviourRole[]Liste des rôles workflow : { id, slug, name, level }.
getMembers()→ BehaviourMember[]Membres du tenant : { id, email, full_name, wf_role_id, wf_role_name, wf_role_slug, wf_role_names[], wf_role_slugs[], attributes }. Un membre peut porter plusieurs rôles (wf_role_slugs) et des attributes de profil personnalisés — la base pour router intelligemment.
// Tous les membres du rôle "Manager" de la région "Sud"
const managers = getMembers().filter(m =>
m.wf_role_slugs.includes('manager') &&
m.attributes && m.attributes.region === 'sud'
)getCurrentUser()→ CurrentUser | nullL’utilisateur connecté : mêmes champs qu’un membre, plus son role plateforme et ses attributes de profil workflow. null si indisponible.
Contexte dossier & inter-dossiers
getCurrentIssue()→ { id, reference, status, priority, title }Métadonnées du dossier ouvert.
getIssueByKey(reference)→ Promise<object | null>async — Recherche un dossier par sa référence ou son UUID. null si introuvable.
getIssuesByTemplate(templateName, filters?)→ Promise<object[]>async — Liste des dossiers d’un template. filters optionnel : { status, priority, search, limit }.
const enCours = await getIssuesByTemplate('Demande de congés', {
status: 'En attente', limit: 100,
})updateIssue(referenceOrId, fieldsData)→ Promise<void>async — Met à jour partiellement les champs d’un autre dossier. fieldsData est un objet { cle: valeur }.
addComment(text)→ Promise<void>async — Ajoute un commentaire au dossier courant (visible dans son historique).
sendEmail(notificationId)→ Promise<void>async — Met en file une notification e-mail préconfigurée (identifiée par son UUID), avec ses destinataires et son gabarit.
Champ grille
Un champ grille porte un petit tableau saisi dans le dossier : des colonnes déclarées sur la définition du champ, des lignes ajoutées par l’utilisateur. Ces quatre fonctions verrouillent une colonne entière ou une cellule précise, sans toucher au reste de la grille. Elles n’existent que dans les comportements.
disableGridColumn(key, colonne) · enableGridColumn(key, colonne)→ voidVerrouille (ou rouvre) une colonne sur toutes les lignes de la grille key. colonne est la clé de colonne déclarée sur le champ.
disableGridCell(key, colonne, ligne) · enableGridCell(key, colonne, ligne)→ voidVerrouille (ou rouvre) une seule cellule. ligne est l’index de la ligne, à partir de 0.
disable(key) sur le champ entier peut donc être contredit par un enableGridCell sur une cellule — une règle générale ne doit jamais écraser une règle particulière, l’inverse serait indéchiffrable pour qui paramètre l’écran.// Le montant se calcule, il ne se saisit pas : colonne verrouillée
disableGridColumn('lignes_facture', 'total_ligne')
// La première ligne est imposée par le contrat : on la fige entièrement
disableGridCell('lignes_facture', 'designation', 0)
disableGridCell('lignes_facture', 'quantite', 0)Objets métier
setBoField(key, instanceId | null)→ voidAffecte un champ Objet métier par UUID d’instance (null pour vider).
searchBoInstances(typeName, query?, limit?)→ Promise<object[]>async — Recherche des instances d’un type d’objet métier par nom de type. query filtre sur le texte, limit par défaut 20. Renvoie un tableau (vide si rien).
// Proposer les 10 clients dont le nom contient la saisie
const clients = await searchBoInstances('Client', getValue('recherche_client'), 10)
setMessage('client', clients.length + ' client(s) trouvé(s)')getBoInstance(typeId, instanceId)→ Promise<object | null>async — Récupère une instance précise.
updateBoInstance(typeId, instanceId, data) · createBoInstance(typeId, data)→ Promise<object | null>async — Met à jour (remplacement complet des attributs) ou crée une instance.
getBoInstance, updateBoInstance et createBoInstance prennent l’identifiant du type d’objet ; searchBoInstances prend son nom. Ces fonctions renvoient null lorsque l’opération n’aboutit pas (fiche introuvable, droits insuffisants) : testez la valeur de retour. Pour ne modifier qu’une partie d’une fiche, utilisez patchBoInstance depuis une post-fonction.Connecteurs & utilitaires
callConnector(connectorId, params?)→ Promise<unknown>async — Exécute un connecteur bxFlow (SOAP ou REST) et renvoie son résultat. Renvoie null en cas d’erreur (à tester). Le résultat REST a la forme { status_code, headers, body } — lisez-le avec lib.get(res, 'body.…').
connectorIdstringUUID du connecteur à exécuter.paramsobject?Paramètres passés au connecteur (query, body…).setMemberAttributes(memberId, attributes)→ Promise<void>async — Remplace l’ensemble des attributs personnalisés d’un membre (profil workflow).
updateUserAttribute(userId, key, value)→ Promise<void>async — Met à jour un seul attribut de profil sans écraser les autres. À préférer à setMemberAttributes quand on ne touche qu’une clé.
const me = getCurrentUser() if (me) await updateUserAttribute(me.id, 'derniere_action', lib.today())
log(...args)→ voidÉcrit un message dans le journal d’exécution, consultable depuis le mode Test de l’éditeur. Sans effet visible pour l’utilisateur — utile pendant la mise au point.
require(name)→ unknownImport sécurisé. require('lib') est toujours disponible ; les autres modules doivent être activés sur le processus (voir require() & catalogue).
// Exemple complet — on_field_change sur 'type_contrat'
if (getValue('type_contrat') === 'CDD') {
show('date_fin'); setRequired('date_fin', true)
} else {
hide('date_fin'); setRequired('date_fin', false)
}
const nb = lib.businessDays(getValue('date_debut'), getValue('date_fin'))
if (nb !== null && nb > 25) setError('date_fin', 'Maximum 25 jours ouvrés')
else { clearError('date_fin'); setValue('nombre_jours', nb ?? 0) }§Conditions — API
Une condition est un court script attaché à une transition, évalué à l’affichage du dossier, qui décide si le bouton apparaît. Elle est en lecture seule : aucun effet de bord n’est possible ici.
Sémantique du retour
| Le script… | Résultat |
|---|---|
return false | Bouton masqué. |
return true / undefined / autre valeur | Bouton visible. |
| lève une erreur | Bouton visible — testez vos conditions. |
| est vide | Bouton visible. |
Fonctions disponibles
Un sous-ensemble volontairement restreint (aucune écriture) : getValue, getCurrentUser, getAssignee, getRoles, getMembers, getCurrentIssue, getIssueByKey, getIssuesByTemplate, callConnector, searchBoInstances, getBoInstance, lib, log. Leur comportement est identique à celui décrit pour les comportements.
// "Clôturer" visible seulement si soldé ET gestionnaire
const solde = Number(getValue('montant_restant') || 0)
const me = getCurrentUser()
return solde === 0 && !!me && me.wf_role_names.includes('Gestionnaire')Conditions déclaratives
Indépendamment du script (qui gère la visibilité), une transition peut porter une condition déclarative qui garde le franchissement réel : si elle n’est pas satisfaite, la transition est refusée, quel que soit l’affichage du bouton. C’est elle qu’il faut utiliser pour une règle qui doit tenir.
| Élément | Valeurs |
|---|---|
condition_type | none · field_condition · connector_result |
condition_op | is_set, is_not_set, eq, ne, gt, gte, lt, lte, contains, not_contains, in, not_in, eq_current_user, ne_current_user |
| Contexte évaluable | Les champs du dossier et les données issues d’un connecteur, plus les champs système préfixés $ : $assigned_to, $priority, $status, $created_by, $current_user. |
§Post-fonctions — API
Les post-fonctions produisent des effets de bord une fois la transition franchie. Après exécution, les champs modifiés (setField) sont persistés et l’assignation appliquée.
getCurrentIssue le reflète.Différences avec les comportements
- L’écriture d’un champ se fait avec
setField(key, value)— passetValue. - Aucune fonction de formulaire vivant : pas de
show/hide/setRequired/disable/enable/setError/setMessage. setAssigneeaccepte un UUID ou un objet{ id }, et valide le format (ignoré si invalide).
Fonctions disponibles
setField(key, value)→ voidModifie un champ du dossier ; la valeur est persistée après l’exécution de la post-fonction.
setAssignee(userId | {id}) · setAssigneeByRole(slugOrName)→ voidAssigne à un utilisateur (par son identifiant) ou à un rôle workflow (message d’erreur si le rôle est introuvable).
getValue · getCurrentIssue · getIssueByKey · getIssuesByTemplate · updateIssue→ —Lecture des champs (BO pré-résolus) et accès inter-dossiers, identiques aux comportements. getCurrentIssue reflète l’état après transition.
addComment(text) · sendEmail(notificationId)→ Promise<void>async — Commente le dossier ; envoie une notification e-mail préconfigurée.
callConnector(connectorId, params?) · setBoField(key, instanceId | null)→ —Exécute un connecteur ; affecte un champ Objet métier (setBoField est persisté avec les autres champs).
searchBoInstances(typeName, query?, limit?) · getBoInstance(typeName, instanceId) · createBoInstance(typeName, data) · updateBoInstance(typeName, instanceId, data)→ Promise<…>async — Référentiels. Ici, le premier argument est le NOM du type (l’UUID reste accepté) : un script exporté dans un .bxflow reste ainsi valable chez le destinataire, là où un UUID y désignerait un type inexistant. Une erreur de droit ou de type lève une exception affichée dans un bandeau — elle n’est pas avalée comme dans un comportement.
patchBoInstance(typeName, instanceId, data)→ Promise<object>async — Met à jour uniquement les attributs fournis, sans toucher au reste de la fiche. C’est presque toujours ce qu’on veut depuis un dossier : écrire une date de revue ou un niveau de risque sans effacer l’adresse et le téléphone saisis ailleurs. updateBoInstance, lui, remplace tout.
// Clôture d'une revue KYC : on ne touche qu'au risque et à la date
await patchBoInstance('Client', getValue('bo_client'), {
niveau_risque_lcbft: getValue('rkyc_nouveau_risque'),
date_derniere_revue: lib.today(),
})setMemberAttributes · updateUserAttribute · getRoles · getMembers · getCurrentUser · lib · log→ —Annuaire, attributs de profil, bibliothèque maison et trace de debug.
// after_transition "Prendre en charge"
const me = getCurrentUser()
if (me) {
setAssignee(me.id)
setField('date_prise_en_charge', lib.today())
await addComment('Pris en charge par ' + (me.full_name || me.email) + '.')
await sendEmail('notif-prise-en-charge-uuid')
}§Recettes concrètes
Des scripts complets, prêts à adapter. Chaque recette indique le contexte et le déclencheur à utiliser.
Congés — calcul auto & contrôle du solde
Motif conditionnel, jours ouvrés calculés, blocage au-delà du solde.
const type = getValue('type_conge')
if (type === 'exceptionnel') { show('motif'); setRequired('motif', true) }
else { hide('motif'); setRequired('motif', false) }
const nb = lib.businessDays(getValue('date_debut'), getValue('date_fin'))
setValue('nombre_jours', nb ?? 0)
const solde = Number(getValue('solde_conges') || 0)
if (nb !== null && nb > solde) {
setError('date_fin', 'Solde insuffisant : ' + nb + ' demandés, ' + solde + ' disponibles')
} else clearError('date_fin')Note de frais — total dynamique & plafond
Sommer des lignes, afficher le total, signaler un dépassement par catégorie.
const lignes = JSON.parse(getValue('lignes_frais') || '[]')
setValue('total', lib.sumBy(lignes, 'montant'))
setMessage('total', lib.currency(lib.sumBy(lignes, 'montant')) + ' sur ' + lignes.length + ' ligne(s)')
const repas = (lib.groupBy(lignes, l => l.categorie).repas) || []
if (repas.length && lib.sumBy(repas, 'montant') > 50) setError('lignes_frais', 'Plafond repas dépassé')
else clearError('lignes_frais')Enrichir via un connecteur (SIREN → raison sociale)
Appeler un connecteur et exploiter son résultat en sécurité.
const siren = String(getValue('siren') || '').replace(/\s/g, '')
if (siren.length === 9) {
const res = await callConnector('annuaire-entreprises-uuid', { siren })
const nom = lib.get(res, 'body.results.0.nom_complet', null)
if (nom) { setValue('raison_sociale', nom); clearError('siren') }
else setError('siren', 'SIREN introuvable')
}« Clôturer » visible seulement si tout est réglé
Masquer le bouton tant qu’il reste des dossiers liés en attente.
const factures = await getIssuesByTemplate('Facture fournisseur', {
status: 'À payer', search: getCurrentIssue().reference, limit: 50,
})
return factures.length === 0 // false → bouton masquéEscalade d’assignation selon le montant
Router vers le bon rôle en fonction d’un seuil.
const montant = Number(getValue('montant') || 0)
setAssigneeByRole(montant > 5000 ? 'direction' : 'manager')
await addComment('Demande de ' + lib.currency(montant) + ' routée automatiquement.')Mettre à jour un dossier lié (budget parent)
Décrémenter le budget d’un dossier « Projet » à la validation.
const ref = getValue('projet_ref')
const projet = await getIssueByKey(ref)
if (projet) {
const budget = Number(lib.get(projet, 'fields_data.budget_restant', 0))
await updateIssue(ref, { budget_restant: Math.max(0, budget - Number(getValue('montant') || 0)) })
}Objets métier (BO)
Rappel : un champ BO lu avec getValue renvoie l’objet complet (pré-résolu), pas l’UUID. À l’écriture, on affecte par UUID d’instance avec setBoField.
Pré-remplir depuis l’instance sélectionnée
Lire les propriétés d’un champ « Véhicule » (BO pré-résolu) pour remplir d’autres champs.
const v = getValue('vehicule') // objet BO complet (pas l'UUID)
if (v) {
setValue('immatriculation', v.immatriculation)
setValue('cout_journalier', v.tarif_jour)
setMessage('vehicule', v.marque + ' ' + v.modele)
}Sélection en cascade (client → contrat)
Quand on choisit un client, proposer automatiquement son contrat.
const client = getValue('client') // BO pré-résolu
if (client) {
const contrats = await searchBoInstances('Contrat', client.reference, 50)
setMessage('contrat', contrats.length + ' contrat(s) pour ce client')
if (contrats.length === 1) setBoField('contrat', contrats[0].id) // affectation par UUID
}Créer une instance à la volée puis la lier
Réutiliser un fournisseur existant, sinon le créer, puis l’affecter au dossier.
const email = getValue('email_fournisseur')
const existants = await searchBoInstances('Fournisseur', email, 1)
let instance = existants[0]
if (!instance) {
instance = await createBoInstance('type-fournisseur-uuid', {
nom: getValue('nom_fournisseur'),
email,
})
}
if (instance) setBoField('fournisseur', instance.id)Mettre à jour une instance (décrément de stock)
À la validation, retirer la quantité commandée du stock de l’article.
const article = getValue('article') // BO pré-résolu
if (article) {
const stock = Number(article.stock || 0) - Number(getValue('quantite') || 0)
await updateBoInstance('type-article-uuid', article.id, { stock: Math.max(0, stock) })
}Bloquer selon un attribut de l’instance
Empêcher l’avancement si le client lié est en liste noire.
const res = await searchBoInstances('Client', getValue('siren'), 1)
const client = res[0]
return !(client && client.blacklist === true) // false → bouton masquéChamps utilisateur & membres
Un champ de type « utilisateur » contient un UUID de membre. Pour l’afficher, on le résout via getMembers(). Pour l’assignation, on utilise setAssignee (utilisateur) ou setAssigneeByRole (rôle).
Pré-sélectionner un utilisateur (champ « valideur »)
Remplir un champ utilisateur avec le manager du demandeur, depuis un attribut de profil.
const me = getCurrentUser()
const managerId = me && me.attributes ? me.attributes.manager_id : null
if (managerId) setValue('valideur', managerId) // champ utilisateur = UUIDAfficher le nom d’un champ utilisateur
Un champ utilisateur contient un UUID ; retrouver le nom via l’annuaire pour informer.
const uid = getValue('valideur')
const membre = getMembers().find(m => m.id === uid)
setMessage('valideur', membre ? 'Validation par ' + (membre.full_name || membre.email) : '')Assigner au bon référent selon un attribut
Router vers le référent RH de la région du dossier, avec repli sur le rôle.
const region = getValue('region')
const referent = getMembers().find(m =>
m.wf_role_slugs.includes('rh') && m.attributes && m.attributes.region === region
)
if (referent) setAssignee(referent.id)
else setAssigneeByRole('rh') // repli si aucun référentAssigner au membre le moins chargé d’un rôle
Répartition simple : compter les dossiers ouverts par membre et choisir le minimum.
const support = getMembers().filter(m => m.wf_role_slugs.includes('support'))
const ouverts = await getIssuesByTemplate('Ticket', { status: 'Ouvert', limit: 500 })
const charge = lib.countBy(ouverts, o => lib.get(o, 'assigned_to', ''))
const cible = lib.minBy(support.map(m => ({ id: m.id, n: charge[m.id] || 0 })), 'n')
if (cible) setAssignee(cible.id)Masquer une action selon le profil courant
N’exposer « Valider budget » qu’aux membres dont le plafond couvre le montant.
const me = getCurrentUser()
const plafond = me && me.attributes ? Number(me.attributes.plafond_validation || 0) : 0
if (Number(getValue('montant') || 0) > plafond) hideTransition('Valider budget')Horodater une action sur le profil
Enregistrer la date de dernière prise en charge sur le profil du membre.
const me = getCurrentUser() if (me) await updateUserAttribute(me.id, 'derniere_prise_en_charge', lib.today())
§require() & catalogue
require(name) est le seul moyen d’importer un module — jamais depuis une URL. Deux niveaux :
require('lib')— la bibliothèque maison, toujours disponible, sans activation.- Catalogue optionnel — modules maison spécifiques, activables par processus. Un
require('x')non activé lève une erreur explicite.
require('lib') n’y figure pas : il est toujours disponible.§Matrice des API
Récapitulatif de la disponibilité de chaque capacité par contexte.
| Capacité | Comportements | Conditions | Post-fonctions |
|---|---|---|---|
getValue | ✓ | ✓ | ✓ |
| écrire un champ | setValue | — | setField |
show/hide/setRequired/disable/enable | ✓ | — | — |
setError/setMessage | ✓ | — | — |
show/hideTransition | ✓ | — | — |
return false masque le bouton | — | ✓ | — |
getAssignee | ✓ | ✓ | — |
setAssignee/setAssigneeByRole | ✓ | — | ✓ |
getRoles/getMembers/getCurrentUser | ✓ | ✓ | ✓ |
getCurrentIssue/getIssueByKey/getIssuesByTemplate | ✓ | ✓ | ✓ |
updateIssue · addComment · sendEmail | ✓ | — | ✓ |
callConnector | ✓ | ✓ | ✓ |
BO : searchBoInstances/getBoInstance | ✓ | ✓ | ✓ |
BO : setBoField/updateBoInstance/createBoInstance | ✓ | — | ✓ |
BO : patchBoInstance (partiel) | — | — | ✓ |
| BO : 1er argument | identifiant du type | nom du type | nom du type |
Grille : disable/enableGridColumn · disable/enableGridCell | ✓ | — | — |
setMemberAttributes/updateUserAttribute | ✓ | — | ✓ |
require('lib') | ✓ | — | — |
lib · log | ✓ | ✓ | ✓ |
Un cas non couvert ?
Cette référence vise l’exhaustivité. S’il manque un exemple ou une précision, dites-le nous — nous complétons la documentation.