Aller au contenu
Documentation technique

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.

ContexteOù on l’écritDéclencheursCe qu’il décide
ComportementsÉditeur de comportements d’un écranon_form_load, on_field_change, on_case_loadL’état vivant du formulaire : valeurs, visibilité, obligation, validation, boutons de transition.
ConditionsChamp « condition » d’une transitionÀ l’affichage du dossierLa visibilité d’un bouton de transition (return false = masqué).
Post-fonctionsOnglet « Post-fonctions » d’une transitionFranchissement d’une transitionLes 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.

▸
Champs « Objet métier » pré-résolus. Pour un champ lié à un objet métier, 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 log et 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.

▸
Pour interroger un système ou un autre dossier, passez par les fonctions injectées (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 | null

Convertit 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?)→ string

Formate une date. fmt vaut 'iso' (défaut, 2026-07-06) ou 'fr' (06/07/2026). Renvoie '' si la date est invalide.

Bibliothèque lib
lib.formatDate('2026-07-06', 'fr')   // → '06/07/2026'
lib.formatDate('n’importe quoi')     // → ''
today()→ string

La date du jour au format ISO. Pratique pour horodater ou calculer une échéance.

addDays(date, n) · subDays(date, n)→ string

Ajoute (ou retire) n jours calendaires. n peut être négatif. Renvoie une date ISO, ou '' si l’entrée est invalide.

Bibliothèque lib
lib.addDays(lib.today(), 30)   // échéance à J+30
lib.subDays('2026-07-06', 7)   // → '2026-06-29'
addMonths(date, n)→ string

Ajoute (ou retire, si n < 0) n mois entiers.

diffDays(a, b)→ number | null

Nombre 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 | null

Nombre de mois entiers entre les deux dates.

businessDays(start, end)→ number | null

Nombre de jours ouvrés (lundi→vendredi) entre deux dates, bornes incluses. Exclut uniquement les week-ends — pas les jours fériés.

Bibliothèque lib
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)
▸
Pour des « ouvrés hors fériés », soustrayez manuellement les fériés connus de votre pays : lib.businessDays(a,b) - nbFeriesDansIntervalle.
daysBetween(start, end)→ number | null

Jours calendaires bornes incluses — équivaut à diffDays + 1. daysBetween('2026-07-01','2026-07-01') vaut 1.

isBefore(a, b) · isAfter(a, b) · isSameDay(a, b)→ boolean

Comparaisons au jour (l’heure est ignorée).

isWeekend(date) · dayOfWeek(date)→ boolean · number

isWeekend : samedi ou dimanche. dayOfWeek : 0 = dimanche … 6 = samedi (-1 si invalide).

startOfMonth(date) · endOfMonth(date)→ string

Premier / 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.

Bibliothèque lib
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)→ number

Somme / moyenne des valeurs. Les valeurs non numériques comptent pour 0 ; meanBy d’un tableau vide vaut 0.

Bibliothèque lib
lib.sumBy(lignes, 'montant')   // total des montants
maxBy(arr, key) · minBy(arr, key)→ T | null

Renvoie 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?)→ unknown

Accè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.

Bibliothèque lib
lib.get(resultatConnecteur, 'body.items.0.id', null)

Format & nombres (locale par défaut fr-FR)

currency(n, code?, locale?)→ string

Formate un montant en devise. code par défaut 'EUR'. lib.currency(1234.5) → 1 234,50 €.

number(n, decimals?, locale?)→ string

Formate un nombre avec séparateurs de milliers. lib.number(1234.5, 2) → 1 234,50.

round(n, decimals?)→ number

Arrondit à decimals décimales (défaut 0). lib.round(3.14159, 2) → 3.14.

clamp(n, min, max)→ number

Borne n entre min et max. lib.clamp(150, 0, 100) → 100.

percent(n, decimals?, locale?)→ string

Formate une proportion en pourcentage. lib.percent(0.75) → 75 %.

capitalize(s) · truncate(s, len, suffix?) · slugify(s)→ string

capitalize('bonjour') → Bonjour · truncate('abcdef', 4) → abc… · slugify('Été à Paris!') → ete-a-paris.

Validation

isEmail(s)→ boolean

Vrai si la string ressemble à une adresse e-mail (contrôle de forme basique, pas de vérification d’existence).

isPhone(s)→ boolean

Vrai pour un numéro de 6 à 30 caractères composé de +, chiffres et séparateurs usuels.

isEmpty(v)→ boolean

Vrai pour null, '', '[]', un tableau vide, ou un objet vide. Pratique pour tester un champ « rempli ».

isNumeric(v)→ boolean

Vrai 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)→ unknown

Valeur 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)→ void

Fixe 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.

Paramètre
keystringClé du champ cible.
valueunknownNouvelle valeur (string, number, tableau, objet BO…).

Afficher, masquer, rendre obligatoire

show(key) · hide(key)→ void

Affiche ou masque un champ. Un champ masqué n’est pas soumis.

setRequired(key, required)→ void

Rend un champ obligatoire (true) ou optionnel (false). Un champ obligatoire vide bloque la soumission.

disable(key) · enable(key)→ void

disable grise le champ (visible mais non éditable) ; enable le réactive. À distinguer de hide (qui le retire).

Validation & messages

setError(key, message)→ void

Marque 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)→ void

Lè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)→ void

Message 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)→ void

Affiche / 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).

Comportement
// 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 } | null

L’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)→ void

Assigne le dossier à un utilisateur par son UUID. null désassigne.

setAssigneeByRole(slugOrName)→ void

Assigne à 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.

Comportement
// 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 | null

L’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 }.

Comportement
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)→ void

Verrouille (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)→ void

Verrouille (ou rouvre) une seule cellule. ligne est l’index de la ligne, à partir de 0.

▸
La règle la plus précise l’emporte : cellule, puis colonne, puis champ. Un 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.
Comportement · on_form_load
// 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)→ void

Affecte 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).

Comportement
// 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.

▸
Dans un comportement, 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.…').

Paramètre
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é.

Comportement
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)→ unknown

Import sécurisé. require('lib') est toujours disponible ; les autres modules doivent être activés sur le processus (voir require() & catalogue).

Comportement
// 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 falseBouton masqué.
return true / undefined / autre valeurBouton visible.
lève une erreurBouton visible — testez vos conditions.
est videBouton 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.

Condition de transition
// "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émentValeurs
condition_typenone · field_condition · connector_result
condition_opis_set, is_not_set, eq, ne, gt, gte, lt, lte, contains, not_contains, in, not_in, eq_current_user, ne_current_user
Contexte évaluableLes 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.
⚠
Règle d’or : le script gouverne l’affichage du bouton (confort UX) ; la condition déclarative gouverne l’autorisation réelle (sécurité). Ne comptez jamais sur le seul script pour empêcher une action.

§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.

▸
Les post-fonctions s’exécutent une fois la transition franchie : le dossier est déjà à sa nouvelle étape, et getCurrentIssue le reflète.

Différences avec les comportements

  • L’écriture d’un champ se fait avec setField(key, value) — pas setValue.
  • Aucune fonction de formulaire vivant : pas de show/hide/setRequired/disable/enable/setError/setMessage.
  • setAssignee accepte un UUID ou un objet { id }, et valide le format (ignoré si invalide).

Fonctions disponibles

setField(key, value)→ void

Modifie un champ du dossier ; la valeur est persistée après l’exécution de la post-fonction.

setAssignee(userId | {id}) · setAssigneeByRole(slugOrName)→ void

Assigne à 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.

Post-fonction · after_transition
// 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.

▸
Le mode Test de l’éditeur (bouton ▶) exécute le script avec des fonctions simulées — il expose le même jeu de fonctions sans effet réel, pour valider la logique avant de l’activer.
Post-fonction
// 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.

Comportement · on_field_change

Congés — calcul auto & contrôle du solde

Motif conditionnel, jours ouvrés calculés, blocage au-delà du solde.

Comportement · on_field_change
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')
Comportement · on_field_change

Note de frais — total dynamique & plafond

Sommer des lignes, afficher le total, signaler un dépassement par catégorie.

Comportement · on_field_change
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')
Comportement · async

Enrichir via un connecteur (SIREN → raison sociale)

Appeler un connecteur et exploiter son résultat en sécurité.

Comportement · async
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')
}
Condition de transition

« Clôturer » visible seulement si tout est réglé

Masquer le bouton tant qu’il reste des dossiers liés en attente.

Condition de transition
const factures = await getIssuesByTemplate('Facture fournisseur', {
  status: 'À payer', search: getCurrentIssue().reference, limit: 50,
})
return factures.length === 0   // false → bouton masqué
Post-fonction · after_transition

Escalade d’assignation selon le montant

Router vers le bon rôle en fonction d’un seuil.

Post-fonction · after_transition
const montant = Number(getValue('montant') || 0)
setAssigneeByRole(montant > 5000 ? 'direction' : 'manager')
await addComment('Demande de ' + lib.currency(montant) + ' routée automatiquement.')
Post-fonction · after_transition

Mettre à jour un dossier lié (budget parent)

Décrémenter le budget d’un dossier « Projet » à la validation.

Post-fonction · after_transition
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.

Comportement · on_field_change

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.

Comportement · on_field_change
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)
}
Comportement · async

Sélection en cascade (client → contrat)

Quand on choisit un client, proposer automatiquement son contrat.

Comportement · async
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
}
Comportement · async

Créer une instance à la volée puis la lier

Réutiliser un fournisseur existant, sinon le créer, puis l’affecter au dossier.

Comportement · async
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)
Post-fonction · after_transition

Mettre à jour une instance (décrément de stock)

À la validation, retirer la quantité commandée du stock de l’article.

Post-fonction · after_transition
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) })
}
Condition de transition · async

Bloquer selon un attribut de l’instance

Empêcher l’avancement si le client lié est en liste noire.

Condition de transition · async
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).

Comportement · on_form_load

Pré-sélectionner un utilisateur (champ « valideur »)

Remplir un champ utilisateur avec le manager du demandeur, depuis un attribut de profil.

Comportement · on_form_load
const me = getCurrentUser()
const managerId = me && me.attributes ? me.attributes.manager_id : null
if (managerId) setValue('valideur', managerId) // champ utilisateur = UUID
Comportement · on_field_change

Afficher le nom d’un champ utilisateur

Un champ utilisateur contient un UUID ; retrouver le nom via l’annuaire pour informer.

Comportement · on_field_change
const uid = getValue('valideur')
const membre = getMembers().find(m => m.id === uid)
setMessage('valideur', membre ? 'Validation par ' + (membre.full_name || membre.email) : '')
Post-fonction · async

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.

Post-fonction · async
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érent
Post-fonction · async

Assigner au membre le moins chargé d’un rôle

Répartition simple : compter les dossiers ouverts par membre et choisir le minimum.

Post-fonction · async
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)
Comportement · on_case_load

Masquer une action selon le profil courant

N’exposer « Valider budget » qu’aux membres dont le plafond couvre le montant.

Comportement · on_case_load
const me = getCurrentUser()
const plafond = me && me.attributes ? Number(me.attributes.plafond_validation || 0) : 0
if (Number(getValue('montant') || 0) > plafond) hideTransition('Valider budget')
Post-fonction · after_transition

Horodater une action sur le profil

Enregistrer la date de dernière prise en charge sur le profil du membre.

Post-fonction · after_transition
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 :

  1. require('lib') — la bibliothèque maison, toujours disponible, sans activation.
  2. Catalogue optionnel — modules maison spécifiques, activables par processus. Un require('x') non activé lève une erreur explicite.
▸
Les modules activables sont proposés dans les paramètres du processus, rubrique « catalogue des librairies ». require('lib') n’y figure pas : il est toujours disponible.

§Matrice des API

Récapitulatif de la disponibilité de chaque capacité par contexte.

CapacitéComportementsConditionsPost-fonctions
getValue✓✓✓
écrire un champsetValue—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 argumentidentifiant du typenom du typenom 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.