SOFTIC Academy

Lot 6 — Demandes et circuits

Mis à jour le 06/11/2026

Le lot 6 apporte un moteur de demandes générique (lot 6-1), puis des types métier livrés dessus (6-2 : notes de frais, ordres de mission avec PDF ; 6-3 : sondages et pointage). Tout repose sur l'organisation du lot 5 : le N+1 et le N+2 sont résolus au dépôt.

6-1 Le moteur (livré, 1.11.0)

 RequestType (par société) : Fields (JSON) + Steps (JSON) + AmountField + PeriodFields
   └──< Request (Number OM-2026-0001, DataJson, Amount, PeriodStart/End, Status, CurrentStep)
           ├──< RequestStep (Order, Kind, ApproverUserId ou Permission, Status, décision, commentaire)
           ├──< RequestEvent (créée, déposée, approuvée, refusée, annulée, commentaire, réattribuée, rappel)
           └──< StoredFile.RequestId (pièces jointes)
 ApprovalDelegation (De → À, période)

Un type = un formulaire + un circuit. Champs : texte, texte long, nombre, montant, date, liste, oui/non, requis ou non. Étapes : N+1, N+2, RH (porteurs de hr.manage), personne nommée, porteurs d'un droit ; chaque étape peut n'exister qu'à partir d'un montant (champ montant du type). Les types livrés par société : Ordre de mission (N+1, puis N+2 au-delà de 2 000), Demande d'acquisition (N+1, puis N+2 au-delà de 5 000), Congé / absence (N+1 puis RH), Autre demande (N+1). Ils restent modifiables, jamais supprimables.

Cycle de vie. Brouillon (validé contre le formulaire) → dépôt : le circuit est instancié avec les approbateurs du moment — N+1 et N+2 de la personne concernée (le demandeur, ou la personne au nom de qui la demande est faite), délégation active comprise — les étapes sous le seuil sont ignorées, celles sans approbateur possible (le DG n'a pas de N+1) ou dont l'approbateur serait le demandeur sont sautées ; sans étape restante la demande est approuvée d'office. Chaque approbation passe la main ; un refus (motivé) clôt. Le demandeur peut annuler tant que ce n'est pas clos ; l'approbateur peut confier l'étape à quelqu'un d'autre ; chacun peut commenter.

Qui voit quoi. Le demandeur (et la personne au nom de qui) : sa demande ; l'approbateur d'une étape, ou un porteur du droit attendu : les demandes qu'il doit décider (et celles qu'il a décidées) ; le gestionnaire des demandes (requests.manage, accordé aux rôles qui administrent les sociétés) : tout, avec le droit d'arbitrer, de réattribuer, de gérer les types et les délégations.

Notifications à chaque transition (cloche, courriel selon les préférences), rappel quotidien des étapes en attente depuis Requests.ReminderDays jours (3), pastille « à valider » dans le menu. Audit : dépôt, décision, annulation, types, délégations.

API (/api/v1/requests/…) : types, counters, mine, to-approve, all, {id}, POST (création, ?submit=true), {id}/submit, {id}/decide, {id}/comment, {id}/cancel.

7-3 Modèles de circuit sur l'organisation fonctionnelle (livré, 1.16.0)

Un modèle de circuit (WorkflowTemplate) est une suite d'étapes réutilisable, par société. Cinq modèles sont livrés : Hiérarchique (N+1), Deux niveaux, Direction (N+1 puis directeur), Ressources humaines, Achats (N+1, directeur au-delà de 5 000, finance). Un type de demande peut suivre un modèle par défaut (« Circuit » dans l'éditeur du type) ou garder son circuit propre.

Les règles d'application (WorkflowRule) appliquent un modèle à une unité de l'organisation et ses sous-unités, à un type (ou tous), au-delà d'un montant. Au dépôt, la règle la plus spécifique l'emporte : unité la plus proche du demandeur dans l'organigramme, puis type précis, puis seuil le plus haut atteint ; sans règle, le modèle du type, sinon son circuit propre (RequestService.ResolveWorkflowAsync).

Nouveaux approbateurs fonctionnels dans les étapes : titulaires d'un poste (tout titulaire actuel du poste peut décider ; un seul titulaire est affecté d'office ; les notifications, l'onglet À valider et la pastille les suivent), responsable de la direction et responsable du département (la plus proche du demandeur en remontant l'organigramme), responsable d'une unité donnée. Une étape dont le décideur serait le demandeur, ou la même personne qu'une étape précédente (N+1 = directeur), est sautée. Le tout se règle dans Demandes → Types de demandes (modèles, règles, étapes), réservé au gestionnaire (requests.manage).

 demande de Saber (Développement ⊂ Direction technique ⊂ DG), acquisition 1 200
   règle « Direction technique · Acquisition · modèle ACHATS-TECH »  → N+1 Imen → titulaire du poste « Responsable achats » Wafa → directeur Rayda
 demande de Wafa (Administration et finances ⊂ DG), formation, type rattaché au modèle DIRECTION
   → N+1 Feki (responsable de la DG) → directeur = Feki : étape sautée

6-2 Notes de frais, impression et archive, congés (livré, 1.12.0)

  • Note de frais : type livré EXPENSE (NF-2026-0001) avec un champ de type lignes (date, nature, détail, montant ; justificatifs en pièces jointes) ; le total est calculé, le circuit est N+1 puis Finance (porteurs de finance.approve, accordé aux rôles qui administrent les sociétés). Export comptable CSV des notes de frais et acquisitions approuvées sur une période (onglet Toutes).
  • PDF natif (lot 7-2) : bouton PDF sur toute demande déposée → GET /requests/{id}/pdf (site) ou GET /api/v1/requests/{id}/pdf (mobile) : A4, en-tête société, champs, lignes de frais, circuit avec « décidé par X le … », pièces jointes. Généré par Requests/Pdf/MiniPdf (générateur PDF minimal sans dépendance) et RequestPdfService. Depuis le lot 7-3, la police SofticSans (sous-ensemble de DejaVu Sans, licence libre, incorporée dans le PDF) couvre le latin et l'arabe : lettres façonnées (formes initiale, médiane, finale, ligatures lam-alef), écriture de droite à gauche, chiffres et mots latins lisibles dans le texte arabe, mise en page en miroir (libellés à droite). Le texte reste extractible et recherchable (table ToUnicode). Archive : à l'approbation finale, ce PDF est figé en pièce jointe de la demande (Requests.ArchiveOnApproval). Le bouton Imprimer (HTML) reste en secours pour une impression rapide depuis le navigateur.
  • Congés : LeaveService — jours ouvrés (week-end Hr.Weekend, jours fériés Hr.Holidays en MM-JJ ou AAAA-MM-JJ, demi-journée), calculés au dépôt (Request.WorkingDays) ; soldes par employé, année et type (LeaveBalances : droits Hr.LeaveDaysPerYear créés d'office, report, pris = demandes approuvées, en attente = déposées) ; solde affiché dans le formulaire de congé ; onglet Congés du dossier employé, ajustable par la RH. À l'approbation, une absence (événement journée entière, public) est créée dans l'agenda de la personne.

6-3 Sondages et pointage (livré, 1.13.0)

Sondages (Engagement/SurveyService, module Sondages, droit surveys.manage accordé aux rôles qui publient des actualités) : questions à choix unique ou multiple, échelle 1-5, texte libre ; cible = toute la société ou une unité et ses sous-unités ; ouverture avec notification des cibles, clôture, date de clôture ; une réponse par personne ; anonymat garanti par construction : la réponse ne porte aucun identifiant, seule la participation (qui a répondu) est enregistrée, dans une table distincte. Résultats agrégés (comptes, moyenne, textes) pour le gestionnaire. API /api/v1/surveys/*.

Pointage (Engagement/AttendanceService, module Pointage, droit attendance.manage accordé aux rôles qui administrent les sociétés) :

  • Pointer : bouton Arrivée / Départ ; règles (pas deux arrivées, pas de départ sans arrivée) ; position du navigateur si autorisée ou exigée (Attendance.RequireLocation), comparée aux sites (rayon en mètres) ; QR du jour de chaque site (« softic-clock:: », jeton = HMAC du secret du site et de la date, valable le jour et la veille) à imprimer sur place et à scanner depuis le mobile.
  • Journées reconstituées à la lecture : première arrivée, dernier départ, heures ; anomalies : arrivée tardive (Attendance.WorkStart
    • tolérance), départ manquant, arrivée manquante, journée courte (Attendance.MinHours), hors site, absence sans congé un jour ouvré (jours fériés et congés approuvés du lot 6-2 pris en compte).
  • Chacun voit ses journées ; le responsable son équipe ; la RH toute la société, anomalies seules, saisie manuelle journalisée, sites et QR, export CSV. API /api/v1/attendance/status|clock|me|team|sites pour le mobile (le lot 7 y ajoute le scan du QR et le hors-ligne).

Lot 14 — Circuit des contrats (1.23.0)

Deux types livrés par société (IsBuiltIn, modifiables, jamais supprimables ; conception docs/LOT14-CONCEPTION.md § 6.1, décision M § 0.6), portés par Engine/HumanResources/ContractService :

  • Renouvellement de contrat CONTRACT_RENEWAL (CR-2026-0001) : champs decision (liste : renouveler, laisser expirer, passer en CDI, obligatoire), newEndDate (date, facultative), comment, contractId (texte, posé par le service) ; circuit N+1 puis RH.
  • Fin de période d'essai CONTRACT_TRIAL (CT-2026-0001) : champs trialDecision (confirmer, prolonger, mettre fin), newTrialEndDate, comment, contractId ; circuit RH seule (Q22).

Options. Les listes decision (renouveler, laisser expirer, passer en CDI) et trialDecision (confirmer, prolonger, mettre fin) sont lues par position par ContractService : leur ordre et leur nombre ne doivent pas être modifiés dans l'administration des types ; seuls les libellés affichés peuvent l'être.

Naissance. Une demande liée à un contrat n'est jamais saisie à la main : la passe quotidienne de relances (ContractService.DailyAsync, seuils par société « 45, 32 » jours avant la fin de contrat, « 10, 7, 4 » avant la fin d'essai) crée au premier seuil franchi un brouillon au nom du N+1 de l'employé (à défaut de la RH), au nom de l'employé, avec le contrat visé dans DataJson.contractId ; le bouton Décider de l'onglet Contrats de la fiche RH (hr.contracts) ouvre ce brouillon ou en crée un au nom du gestionnaire. SaveDraftAsync verrouille contractId (valeur enregistrée conservée, valeur saisie ignorée, aucune à la création manuelle). Le N+1 ouvre le brouillon depuis sa notification (/requests/{id}), saisit la décision et dépose : il est le demandeur, son étape est sautée, la RH valide.

Approbation. À l'approbation finale, RequestService.OnApprovedAsync appelle les IApprovalHandler enregistrés (lot 14) : ContractService.ApplyDecisionAsync écrit le contrat pour une société gérée par l'intranet (renouvellement : contrat suivant ; CDI : contrat Permanent ; essai : nouvelle fin d'essai ou contrat clos du jour), et pour une société gérée par IES ne fait qu'ouvrir une tâche et notifier la RH (« à saisir dans IES, référence <numéro> ») : le contrat revient par l'import. Une ligne de journal (ContractReminderLogs, genre 3 / 4, RequestId unique) marque la décision appliquée ; la passe quotidienne rejoue les demandes approuvées sans marqueur, une exception du crochet ne perd donc aucune décision. Détail : docs/ARCHITECTURE.md, « Lot 14, étape 5 ».