V1.14.15 Mise en plce des consignes et premiers tests

This commit is contained in:
2026-09-07 06:16:51 +00:00
parent 3d56de8bfa
commit d287607e45
8 changed files with 304 additions and 21 deletions
+57
View File
@@ -0,0 +1,57 @@
<?php
/**
* ASPIC — consignes_fonctions.php
*
* Bibliothèque des fonctions du catalogue ASP_Fonctions (chantier des
* consignes, 6 septembre 2026 — voir Réflexions stratégiques). Chaque
* fonction ci-dessous porte le nom exact enregistré dans
* ASP_Fonctions.FON_NomPHP, et est appelée dynamiquement par le moteur
* générique (voir moteur_consignes.php) — plus aucun appel en dur dans
* contacts.php depuis la version du 6 septembre 2026 (soir).
*
* Convention pour écrire une nouvelle fonction ici :
* - Traitement : signature (string $valeur): string — reçoit la valeur
* actuelle du champ, retourne la valeur transformée.
* - Test : signature (string $valeur): bool — retourne vrai si la valeur
* respecte la consigne, faux sinon (le message affiché à l'échec vient de
* CNS_MessageErreur en base, jamais de cette fonction).
* Fichier volontairement sans aucune dépendance (pas de session, pas de
* base de données) : chaque fonction ne fait que transformer ou tester une
* valeur qu'on lui passe.
*/
// ASP_Fonctions.FON_Nom = 'MAJUSCULE' (Nature=Traitement)
// Met en majuscules la valeur d'un champ, en respectant les caractères
// accentués français (mb_strtoupper avec encodage explicite : un simple
// strtoupper() laisserait "é"/"è"/... inchangés selon la locale du serveur).
function consigne_majuscule(string $valeur): string
{
return mb_strtoupper($valeur, 'UTF-8');
}
// ASP_Fonctions.FON_Nom = 'NON_VIDE' (Nature=Test)
// Vrai si la valeur n'est pas vide une fois débarrassée des espaces
// superflus en début/fin (une chaîne de simples espaces est considérée vide).
function consigne_non_vide(string $valeur): bool
{
return trim($valeur) !== '';
}
// ASP_Fonctions.FON_Nom = 'PREMIERE_LETTRE_MAJUSCULE' (Nature=Traitement)
// "georges" -> "Georges", "JOSEPH" -> "Joseph", "jean-marc" -> "Jean-Marc".
// Tout le reste en minuscules, une majuscule après le début de la chaîne et
// après chaque tiret ou espace (pour les prénoms composés). Volontairement
// pas de ucwords() : cette fonction native de PHP n'est pas fiable en UTF-8
// (elle raisonne en octets, pas en caractères — un prénom commençant par une
// lettre accentuée type "Éric" serait corrompu).
function consigne_premiere_lettre_majuscule(string $valeur): string
{
$valeur = mb_strtolower($valeur, 'UTF-8');
return (string)preg_replace_callback(
'/(^|[\s\-])(\p{L})/u',
function (array $m): string {
return $m[1] . mb_strtoupper($m[2], 'UTF-8');
},
$valeur
);
}
+23 -6
View File
@@ -56,11 +56,26 @@
* doit appeler `session_start()` et renseigner `$_SESSION['USR_Code']` au * doit appeler `session_start()` et renseigner `$_SESSION['USR_Code']` au
* moment où l'identifiant/mot de passe sont validés ; sans cette mise à jour * moment où l'identifiant/mot de passe sont validés ; sans cette mise à jour
* côté `aspic.php`, plus aucun appel à ce fichier ne pourra aboutir. * côté `aspic.php`, plus aucun appel à ce fichier ne pourra aboutir.
*
* ⚙️ Chantier des consignes (6 septembre 2026, voir Réflexions stratégiques) :
* POST et PUT n'ont plus aucun contrôle ni traitement écrit en dur sur les
* champs — un seul appel à appliquerConsignes() (moteur_consignes.php), qui
* lit ASP_Consignes/ASP_Fonctions à chaque appel et applique ce qu'il y
* trouve pour le contexte 'Contacts' (portée 'Création' ou 'Modification'
* selon le cas). Ajouter/modifier une consigne en base change donc le
* comportement réel sans toucher ce fichier — sauf si une fonction nécessaire
* n'existe pas encore dans consignes_fonctions.php, ou en cas de bug.
* Version volontairement simplifiée (voir en-tête de moteur_consignes.php) :
* pas de distinction Saisie/Validation (tout s'applique à l'enregistrement),
* pas de vérification en temps réel côté écran pour l'instant — décision de
* Jean-Marc du 6 septembre 2026, priorité à la robustesse.
*/ */
session_start(); session_start();
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
require __DIR__ . '/db.php'; require __DIR__ . '/db.php';
require __DIR__ . '/consignes_fonctions.php';
require __DIR__ . '/moteur_consignes.php';
// Accès réservé aux utilisateurs identifiés (session ouverte par aspic.php). // Accès réservé aux utilisateurs identifiés (session ouverte par aspic.php).
if (empty($_SESSION['USR_Code'])) { if (empty($_SESSION['USR_Code'])) {
@@ -183,10 +198,11 @@ try {
case 'POST': case 'POST':
$data = json_decode(file_get_contents('php://input'), true) ?? []; $data = json_decode(file_get_contents('php://input'), true) ?? [];
$nom = trim($data['CNT_Nom'] ?? '');
if ($nom === '') { $erreurConsigne = appliquerConsignes($pdo, 'Contacts', 'Création', $data);
if ($erreurConsigne !== null) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Le nom est obligatoire']); echo json_encode(['error' => $erreurConsigne]);
exit; exit;
} }
@@ -239,10 +255,11 @@ try {
exit; exit;
} }
$data = json_decode(file_get_contents('php://input'), true) ?? []; $data = json_decode(file_get_contents('php://input'), true) ?? [];
$nom = trim($data['CNT_Nom'] ?? '');
if ($nom === '') { $erreurConsigne = appliquerConsignes($pdo, 'Contacts', 'Modification', $data);
if ($erreurConsigne !== null) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Le nom est obligatoire']); echo json_encode(['error' => $erreurConsigne]);
exit; exit;
} }
+12 -3
View File
@@ -57,10 +57,11 @@ Pour les trois documents de ce projet (ce journal — résumé/index —, les R
- 27. Pilotage vocal - 27. Pilotage vocal
- 28. Écran de fond - 28. Écran de fond
- 29. Interprétation vocale des contacts par LLM - 29. Interprétation vocale des contacts par LLM
- 30. Chantier des consignes — moteur générique
*(Numérotation et titres repris du découpage fait par Jean-Marc le 31 août 2026, légèrement reformulés par lui à cette occasion — se référer aux fichiers eux-mêmes pour le libellé exact. Section 27 (Pilotage vocal) validée de bout en bout et son fichier définitif déposé dans `/journal` le 2 septembre 2026. Section 28 (Écran de fond) ouverte le 2 septembre 2026, toujours **en cours** — passée d'une maquette graphique exploratoire à un premier essai fonctionnel réel, puis (soirée du 2 au 3 septembre) à une pastille de comptage sur le classeur Contacts et à un premier affichage réel des post-its personnels sur le tableau de liège. Section 29 (Interprétation vocale des contacts par LLM) ouverte et son fichier définitif déposé le 4 septembre 2026 après validation de bout en bout sur un premier corpus réel ; fichiers de section à déposer par Jean-Marc, voir Statut actuel.)* *(Numérotation et titres repris du découpage fait par Jean-Marc le 31 août 2026, légèrement reformulés par lui à cette occasion — se référer aux fichiers eux-mêmes pour le libellé exact. Section 27 (Pilotage vocal) validée de bout en bout et son fichier définitif déposé dans `/journal` le 2 septembre 2026. Section 28 (Écran de fond) ouverte le 2 septembre 2026, toujours **en cours** — passée d'une maquette graphique exploratoire à un premier essai fonctionnel réel, puis (soirée du 2 au 3 septembre) à une pastille de comptage sur le classeur Contacts et à un premier affichage réel des post-its personnels sur le tableau de liège. Section 29 (Interprétation vocale des contacts par LLM) ouverte et son fichier définitif déposé le 4 septembre 2026 après validation de bout en bout sur un premier corpus réel ; fichiers de section à déposer par Jean-Marc, voir Statut actuel. Section 30 (Chantier des consignes — moteur générique) ouverte le 6 septembre 2026, fichier définitif rédigé le soir même après validation de bout en bout du mécanisme sur des cas réels ; fichier de section à déposer par Jean-Marc, voir Statut actuel.)*
## Statut actuel (2-4 septembre 2026) ## Statut actuel (2-6 septembre 2026)
**Écrans "Identification" et "Liste des contacts"** (avec panneau détail intégré) : aboutis depuis le 25 août 2026, testés en production. **Prospection** (formulaire externe, vérification mail, envoi de doc) : livrée et testée en conditions réelles le 26 août 2026 (section 18). **Dépôt Git** en place depuis le 27-28 août (section 20), avec circuit de livraison FileZilla → SSH VPS → `git push` (section 23) — l'écriture directe par Claude reste bloquée côté proxy Anthropic, contournement adopté sans gêne. **MIRADOR** (authentification centralisée multi-bases, identifiant = email) branché et validé à deux utilisateurs le 30 août 2026, déployé sur `aspic.mvl-tls.fr` (développement) et `crm.aspic.bzh` (test), chacun avec son propre environnement Docker (sections 21, 24, 25). **Écrans "Identification" et "Liste des contacts"** (avec panneau détail intégré) : aboutis depuis le 25 août 2026, testés en production. **Prospection** (formulaire externe, vérification mail, envoi de doc) : livrée et testée en conditions réelles le 26 août 2026 (section 18). **Dépôt Git** en place depuis le 27-28 août (section 20), avec circuit de livraison FileZilla → SSH VPS → `git push` (section 23) — l'écriture directe par Claude reste bloquée côté proxy Anthropic, contournement adopté sans gêne. **MIRADOR** (authentification centralisée multi-bases, identifiant = email) branché et validé à deux utilisateurs le 30 août 2026, déployé sur `aspic.mvl-tls.fr` (développement) et `crm.aspic.bzh` (test), chacun avec son propre environnement Docker (sections 21, 24, 25).
@@ -98,6 +99,8 @@ Version incrémentée une seule fois pour l'ensemble de cette session (pastille
**4 septembre 2026 : interprétation vocale des contacts par un LLM à sortie structurée (V1.012).** Détail complet en section 29 du journal. Résumé : premier outil réel d'interprétation, dans le prolongement de la banque de dictées "Gestion des Contacts" — `contact_ia_interprete.php` (appel OpenAI `gpt-5.6-luna`, JSON Schema strict construit à partir des tables réelles ASP_Statuts/ASP_Professions/ASP_Agents/ASP_Actions, re-validation défensive après réponse), `contact_ia_valide.php` (seul point d'écriture, table à part `ASP_PropositionsContact` — ne crée jamais de contact ni de profession réels), `contact_ia_panel.js` (panneau flottant sur le micro de l'écran de fond, remplace `micro_test.js`). Décision architecturale actée : l'attribution d'un assistant par défaut quand aucun n'est nommé relève d'un futur traitement n8n, pas de cette couche d'interprétation qui doit rester un enregistreur honnête de ce qui est réellement dit. Détection de l'action "ajouter le contact" (libellé fixe, toujours classée en premier) confirmée comme une vraie décision du LLM porté par le prompt, pas un cas codé en dur — vérifié avec Jean-Marc. Zone actions simplifiée en 3 champs de texte libre (au lieu d'un motif dynamique existant/nouveau par ligne, jugé trop lourd pour cet essai). Nombreuses itérations d'ergonomie du panneau : dimensionnement immédiat à la taille maximale disponible avec défilement unique, lisibilité des listes déroulantes, arrêt automatique du micro après 3,5s de silence, redémarrage automatique du micro et panneau qui reste ouvert après validation (pour dicter plusieurs contacts à la suite), bouton "Abandonner" retiré au profit d'Échap seul. Relecture d'un premier corpus réel (11 essais) : résultats globalement encourageants, un bug d'extraction identifié (adresse mail non nettoyée d'un mot d'appel et d'un accent) et une possible troncature de fin de dictée par le minuteur de silence restent des points ouverts, non corrigés à la clôture. Jean-Marc a par ailleurs formulé une réflexion pour une prochaine session : séparer formellement les actions utilisables par l'utilisateur humain (menu/clavier) de celles utilisables par le LLM vocal, avec un vocabulaire d'actions unique et cohérent entre les deux voies d'accès. Version incrémentée une seule fois pour l'ensemble de cette session : **V1.012**. **4 septembre 2026 : interprétation vocale des contacts par un LLM à sortie structurée (V1.012).** Détail complet en section 29 du journal. Résumé : premier outil réel d'interprétation, dans le prolongement de la banque de dictées "Gestion des Contacts" — `contact_ia_interprete.php` (appel OpenAI `gpt-5.6-luna`, JSON Schema strict construit à partir des tables réelles ASP_Statuts/ASP_Professions/ASP_Agents/ASP_Actions, re-validation défensive après réponse), `contact_ia_valide.php` (seul point d'écriture, table à part `ASP_PropositionsContact` — ne crée jamais de contact ni de profession réels), `contact_ia_panel.js` (panneau flottant sur le micro de l'écran de fond, remplace `micro_test.js`). Décision architecturale actée : l'attribution d'un assistant par défaut quand aucun n'est nommé relève d'un futur traitement n8n, pas de cette couche d'interprétation qui doit rester un enregistreur honnête de ce qui est réellement dit. Détection de l'action "ajouter le contact" (libellé fixe, toujours classée en premier) confirmée comme une vraie décision du LLM porté par le prompt, pas un cas codé en dur — vérifié avec Jean-Marc. Zone actions simplifiée en 3 champs de texte libre (au lieu d'un motif dynamique existant/nouveau par ligne, jugé trop lourd pour cet essai). Nombreuses itérations d'ergonomie du panneau : dimensionnement immédiat à la taille maximale disponible avec défilement unique, lisibilité des listes déroulantes, arrêt automatique du micro après 3,5s de silence, redémarrage automatique du micro et panneau qui reste ouvert après validation (pour dicter plusieurs contacts à la suite), bouton "Abandonner" retiré au profit d'Échap seul. Relecture d'un premier corpus réel (11 essais) : résultats globalement encourageants, un bug d'extraction identifié (adresse mail non nettoyée d'un mot d'appel et d'un accent) et une possible troncature de fin de dictée par le minuteur de silence restent des points ouverts, non corrigés à la clôture. Jean-Marc a par ailleurs formulé une réflexion pour une prochaine session : séparer formellement les actions utilisables par l'utilisateur humain (menu/clavier) de celles utilisables par le LLM vocal, avec un vocabulaire d'actions unique et cohérent entre les deux voies d'accès. Version incrémentée une seule fois pour l'ensemble de cette session : **V1.012**.
**6 septembre 2026 : chantier des consignes — un moteur générique pour sortir du code en dur (V1.14.15).** Détail complet en section 30 du journal, fil conceptuel complet en Réflexions stratégiques, section 15. Résumé : reprise le soir même, sous forme concrète, de la distinction règles/consignes esquissée le matin — première mise en œuvre volontairement circonscrite à la fiche Contacts (création/modification). Deux tables créées par Jean-Marc (`ASP_Fonctions` : catalogue de fonctions génériques ; `ASP_Consignes` : instructions actives par contexte/champ), un moteur générique unique (`moteur_consignes.php`, fonction `appliquerConsignes()`) qui lit ces tables à l'exécution et applique traitements (mise en forme silencieuse) puis tests (validation, avec message d'erreur personnalisé), et une bibliothèque de fonctions du catalogue (`consignes_fonctions.php` : `MAJUSCULE`, `NON_VIDE`, `PREMIERE_LETTRE_MAJUSCULE`). `contacts.php` réduit à un seul appel au moteur, plus aucun contrôle de champ écrit en dur. Détour initial corrigé par Jean-Marc (Claude avait d'abord codé en dur un contrôle par champ au lieu du moteur générique demandé), puis abandon délibéré de la validation en temps réel côté écran au profit de la robustesse — révélant au passage un bug préexistant (message d'erreur générique affiché au lieu du message réel du serveur), corrigé. Ajustements d'interface demandés en cours de test : toast d'erreur centré et rouge sur le panneau de saisie, rendu correct des retours à la ligne saisis dans un message d'erreur. Preuve de concept validée par Jean-Marc en fin de session : ajout de nouvelles consignes (`NON_VIDE` sur Prénom et Ville) sans la moindre modification de fichier PHP/JS, comportement immédiatement actif avec les fichiers déjà livrés. Version incrémentée une seule fois pour l'ensemble de cette session, sur les deux compteurs anticipés dès le début (code et conception) : **V1.14.15**.
**Reste à faire, noté au passage :** **Reste à faire, noté au passage :**
- Décider s'il faut renforcer le prompt d'extraction pour neutraliser les mots parasites/formules de politesse dictés autour d'une adresse mail (bug identifié le 4 septembre sur le corpus réel, correction non encore actée par Jean-Marc). - Décider s'il faut renforcer le prompt d'extraction pour neutraliser les mots parasites/formules de politesse dictés autour d'une adresse mail (bug identifié le 4 septembre sur le corpus réel, correction non encore actée par Jean-Marc).
- Clarifier l'origine de la coupure de fin de dictée observée sur un essai du 4 septembre (minuteur de silence à 3,5s vs interruption réelle) avant de décider si `DUREE_SILENCE_ARRET_MS` (`contact_ia_panel.js`) doit être allongée. - Clarifier l'origine de la coupure de fin de dictée observée sur un essai du 4 septembre (minuteur de silence à 3,5s vs interruption réelle) avant de décider si `DUREE_SILENCE_ARRET_MS` (`contact_ia_panel.js`) doit être allongée.
@@ -113,7 +116,7 @@ Version incrémentée une seule fois pour l'ensemble de cette session (pastille
- Reconnecter `micro.js` (vrai pilotage) sur le micro de l'écran de fond une fois la phase de mise au point terminée — une ligne à changer dans `ecran_fond.html`. - Reconnecter `micro.js` (vrai pilotage) sur le micro de l'écran de fond une fois la phase de mise au point terminée — une ligne à changer dans `ecran_fond.html`.
- Étendre `MARQUEUR_FIN_LIGNE` dans `micro.js` pour accepter "puis" en plus de "ensuite" (retour d'usage de Jean-Marc, 3 septembre) — sans urgence. - Étendre `MARQUEUR_FIN_LIGNE` dans `micro.js` pour accepter "puis" en plus de "ensuite" (retour d'usage de Jean-Marc, 3 septembre) — sans urgence.
- Continuer à observer les agents actifs (Viktor, Matilda, Léon, Nikita) en usage réel ; ajuster `AGN_Variantes` en base si de nouvelles confusions apparaissent. - Continuer à observer les agents actifs (Viktor, Matilda, Léon, Nikita) en usage réel ; ajuster `AGN_Variantes` en base si de nouvelles confusions apparaissent.
- Documentation utilisateur : le panneau de détail du contact, la ToDoList et le pilotage vocal restent à documenter (mise à jour du 3 septembre limitée à l'écran d'accueil et à la liste des contacts) — l'outil de banque de dictées n'a volontairement pas vocation à y figurer, ce n'est pas un module utilisateur. - Documentation utilisateur : le panneau de détail du contact, la ToDoList et le pilotage vocal restent à documenter (mise à jour du 3 septembre limitée à l'écran d'accueil et à la liste des contacts) — l'outil de banque de dictées et le chantier des consignes n'ont volontairement pas vocation à y figurer pour l'instant, ce sont des chantiers internes, pas des modules utilisateur finalisés.
- Écran de fond : reconfirmer l'état de la "tranche du bureau" après le recadrage du 3 septembre (a pu déplacer le problème sans que ce soit son objet) ; décider si les autres objets photographiés (carnet, téléphone) doivent devenir cliquables ; trancher un jour le sort de la piste "maquette liège + post-its par agent" (Claude Design, mise de côté pour cet essai, pas explicitement abandonnée). - Écran de fond : reconfirmer l'état de la "tranche du bureau" après le recadrage du 3 septembre (a pu déplacer le problème sans que ce soit son objet) ; décider si les autres objets photographiés (carnet, téléphone) doivent devenir cliquables ; trancher un jour le sort de la piste "maquette liège + post-its par agent" (Claude Design, mise de côté pour cet essai, pas explicitement abandonnée).
- Concevoir les deux signes graphiques distincts pour les post-its "à corriger"/"à valider" (réflexions stratégiques, section 12) — le premier acté (point d'interrogation), le second encore à choisir. - Concevoir les deux signes graphiques distincts pour les post-its "à corriger"/"à valider" (réflexions stratégiques, section 12) — le premier acté (point d'interrogation), le second encore à choisir.
- Tableau de liège : coder la répartition dynamique des 70% entre assistants par charge de tâches (formule actée, voir Statut actuel) — bloqué tant que la ToDoList des assistants n'existe pas réellement. - Tableau de liège : coder la répartition dynamique des 70% entre assistants par charge de tâches (formule actée, voir Statut actuel) — bloqué tant que la ToDoList des assistants n'existe pas réellement.
@@ -122,3 +125,9 @@ Version incrémentée une seule fois pour l'ensemble de cette session (pastille
- Revoir les règles de calcul des pastilles rouge/orange/vert (`calculerGroupe()`) pour éviter un tableau trop chargé en rouge/orange maintenant que le vert quitte le tableau immédiatement. - Revoir les règles de calcul des pastilles rouge/orange/vert (`calculerGroupe()`) pour éviter un tableau trop chargé en rouge/orange maintenant que le vert quitte le tableau immédiatement.
- Décider du comportement du clic sur un post-it (aujourd'hui inerte) : ouvrir `liste_todolist.html` sur la tâche précise, ou afficher un détail directement sur l'écran de fond. - Décider du comportement du clic sur un post-it (aujourd'hui inerte) : ouvrir `liste_todolist.html` sur la tâche précise, ou afficher un détail directement sur l'écran de fond.
- Capacité du tableau (20) et taille de la grille (4×5) dans `ecran_fond.html`/`todolist.php` sont des valeurs provisoires posées avant d'avoir les vrais pixels de la version finale — à ajuster ensemble après usage réel prolongé. - Capacité du tableau (20) et taille de la grille (4×5) dans `ecran_fond.html`/`todolist.php` sont des valeurs provisoires posées avant d'avoir les vrais pixels de la version finale — à ajuster ensemble après usage réel prolongé.
- Concevoir un nouveau type de consigne pour les cas nécessitant un appel IA en cours de traitement (ex. retrouver le code postal d'une ville saisie sans code postal) — contrat de retour plus riche que Traitement/Test actuel (silencieux / besoin de précision / question à poser), gestion d'échec réseau, coût et latence d'un appel externe (chantier des consignes, section 30) — session dédiée à prévoir.
- Écran de gestion des consignes par le boss (au lieu d'`INSERT`/`UPDATE` SQL à la main) — à construire après stabilisation du contrat ci-dessus, pour ne pas le refaire (chantier des consignes, section 30).
- Réflexion sur un catalogue restreint de consignes/fonctions avancées à proposer aux futurs utilisateurs de l'agence, sans leur ouvrir la création libre de A à Z — argument commercial identifié par Jean-Marc le 6 septembre (voir réflexions stratégiques, section 15).
- Accumulation de plusieurs messages d'erreur à la fois dans le moteur de consignes (aujourd'hui, seul le premier test en échec est remonté).
- Gestion des consignes transverses à toute la fiche (`CNS_Champ` NULL, ex. contrôle de doublon) — non traitée par le moteur de consignes actuel.
- Distinction Saisie/Validation réellement exploitée un jour dans le moteur de consignes (temps réel), si le besoin redevient assez fort pour justifier la complexité écartée le 6 septembre.
@@ -205,3 +205,28 @@ Point explicitement acté avec Jean-Marc : **aucune solution, gratuite ou payant
- Un corpus de consignes commun aux quatre assistants, ou un livret propre à chacun selon son `AGN_Fonction`. - Un corpus de consignes commun aux quatre assistants, ou un livret propre à chacun selon son `AGN_Fonction`.
- Le périmètre exact du journalier (actions des assistants seulement, ou humain + assistants comme envisagé initialement le 4 septembre pour l'écran journalier du journal de développement). - Le périmètre exact du journalier (actions des assistants seulement, ou humain + assistants comme envisagé initialement le 4 septembre pour l'écran journalier du journal de développement).
- La fréquence d'alimentation du journalier par n8n (par demi-journée évoquée par Jean-Marc, ou calée sur le rythme réel de l'agent de veille dont le réglage reste, comme noté en section 12, à choisir plus tard) — non encore décidée par Jean-Marc au 6 septembre. - La fréquence d'alimentation du journalier par n8n (par demi-journée évoquée par Jean-Marc, ou calée sur le rythme réel de l'agent de veille dont le réglage reste, comme noté en section 12, à choisir plus tard) — non encore décidée par Jean-Marc au 6 septembre.
## 15. Sortir du code en dur : règles et consignes comme mécanisme générique, premier cas construit sur la fiche Contacts (6 septembre 2026)
**Origine — le prolongement naturel de la session de réflexion du matin même.** Dans la continuité directe de la section 14 (le pilotage vocal comme deuxième mode de fonctionnement, consignes en langage naturel plutôt que règles programmées), Jean-Marc a voulu, le soir du même jour, poser une première brique concrète et volontairement modeste de cette philosophie — pas encore le pipeline vocal à quatre phases de la section 14, mais son fondement le plus basique : une fiche (Contacts, en création/modification) dont le comportement de validation et de mise en forme n'est plus écrit dans le code, mais lu dans des données.
**La distinction posée en ouverture de session : règles vs consignes.** Une **règle** est intrinsèque à l'application, non modifiable par l'utilisateur, parfois visible et parfois totalement transparente pour lui (ex. un contrôle d'intégrité que Jean-Marc seul déciderait d'activer). Une **consigne** est éditable par l'utilisateur, en langage naturel dans son intitulé, à l'image d'une instruction donnée à une véritable assistante — ce que Jean-Marc a nommé, en cours de session, "Mathilda" pour parler concrètement de cette couche. Deux natures pour une consigne : **Traitement** (transformation silencieuse d'une valeur, ex. mise en majuscules) et **Test** (validation, avec un message d'erreur si elle échoue). Cette distinction règles/consignes, et celle Traitement/Test, sont directement héritées de la réflexion du matin (section 14) mais franchissent ici, pour la première fois, le seuil du code réel.
**Le principe non négociable, formulé sans ambiguïté par Jean-Marc après une première tentative manquée de Claude (voir plus bas) : "sortir du code en dur".** L'objectif explicite n'était pas de coder telle ou telle validation, aussi propre soit-elle, mais de construire un **moteur générique** qui lit deux tables (le catalogue de fonctions disponibles, et les consignes actives qui s'appuient dessus) et qui exécute ce qu'il y trouve — de sorte qu'ajouter, modifier ou désactiver une consigne change le comportement réel de l'application **sans qu'aucune ligne de code ne soit retouchée**, sauf dans deux cas seulement assumés comme limites naturelles du système : l'écriture d'une fonction réellement nouvelle dans le catalogue, ou la correction d'un bug. Dans les mots de Jean-Marc, précisant ce qu'il ne demandait pas : *"Je n'ai aucun doute sur la capacité à programmer les tests et fonctions que tu as mis en place. Ce n'est pas cela que je souhaite tester, c'est bien toute la mécanique [...] : brancher des consignes, des règles sur la mise à jour des contacts [...] en sortant du code en dur !"*
**Un détour instructif, corrigé en cours de route.** Face à la première demande de Jean-Marc (rendre le Nom obligatoire, le mettre en majuscules), Claude a d'abord câblé ces deux comportements directement dans `contacts.php`, en se disant que le cas concret viendrait avant la généralisation. Erreur de lecture du besoin, corrigée fermement par Jean-Marc : ce n'était pas la complexité de la règle qui était en jeu, mais l'endroit où elle devait vivre. Reconstruction immédiate autour d'un moteur générique unique lisant les deux tables à l'exécution, sans plus aucune référence à un champ ou une consigne précise dans le code (voir journal de développement, section 30, pour le détail technique). Ce détour, bien que corrigé rapidement, mérite d'être consigné ici : il illustre concrètement la différence entre "avoir compris l'objectif en théorie" et "construire le bon niveau d'abstraction du premier coup" — un risque qui se représentera à chaque nouvelle brique de cette philosophie plus large.
**Un arbitrage assumé : la robustesse plutôt que le temps réel.** Une fois le moteur générique posé, restait la question du moment d'exécution : au clavier, au fil de la saisie (retour immédiat, "Saisie"), ou seulement à l'enregistrement ("Validation"). Jean-Marc, dépourvu selon ses propres mots de la compétence pour juger du coût réel d'un contrôle en temps réel, a choisi de trancher sur le critère qui compte le plus pour lui à ce stade du projet plutôt que sur l'exhaustivité fonctionnelle : *"je cherche aussi à ce que l'exécution soit fluide et que le code ne soit pas propice à des plantages"*. Décision : abandonner, pour cette première version du moteur, toute distinction Saisie/Validation et toute réaction en temps réel — une consigne active s'applique uniquement à l'enregistrement, quel que soit son moment déclaré en base. Ce choix a eu un effet inattendu et bienvenu : en retirant l'ancien contrôle client codé en dur, il a révélé un bug préexistant et jusque-là invisible (le message d'erreur affiché à l'écran était toujours générique, jamais celui réellement renvoyé par le serveur) — une illustration concrète du principe déjà éprouvé sur ce projet : simplifier expose parfois des défauts que la complexité précédente masquait, plutôt que d'en créer de nouveaux.
**L'enseignement retenu pour l'avenir, au-delà du cas des contacts : mécanisme générique et données propres à un client ne doivent jamais se mélanger.** En discutant de l'extension possible du catalogue de fonctions à des cas plus élaborés (ex. une liste de préfixes de codes postaux propre à une zone géographique), le principe suivant a été posé comme garde-fou pour la suite : le catalogue de fonctions (`ASP_Fonctions`) ne doit contenir que des primitives **génériques et paramétrables** (ex. "la valeur appartient à telle liste"), jamais une valeur métier figée pour un client donné — celle-ci doit toujours vivre comme une donnée de consigne (`CNS_Parametres`), jamais comme du code nouveau. Ce point prend une importance particulière à la lumière du modèle SaaS envisagé pour ASPIC (section 5) : le jour où plusieurs clients partageront le même catalogue de fonctions, ce catalogue devra rester strictement neutre vis-à-vis de chacun d'eux — toute valeur propre à un client doit résider dans ses propres consignes, jamais s'infiltrer dans le mécanisme commun.
**Validation par la pratique, sans qu'aucun code ne soit retouché après livraison.** Une fois le moteur et le catalogue de premières fonctions (mise en majuscule, non-vide, première lettre en majuscule) livrés, Jean-Marc a lui-même ajouté deux nouvelles consignes en base (non-vide sur le Prénom, puis sur la Ville) — comportement immédiatement actif, aucun fichier applicatif retouché. Conclusion de Jean-Marc, dans ses mots : *"On peut dire qu'on n'est pas mauvais !"* — première preuve concrète, à petite échelle, que la philosophie de la section 14 (des consignes plutôt que des écrans à embranchements) est réalisable technique­ment, pas seulement séduisante sur le papier.
**Une extension entrevue mais explicitement mise de côté pour ce soir : l'appel à l'IA au sein même d'une consigne.** Jean-Marc a proposé, en fin de session, une consigne bien plus élaborée que celles construites jusqu'ici : si une ville est saisie sans code postal, aller chercher ce code postal via un appel à l'IA. Contrairement aux consignes du soir (Traitement pur ou Test pur, réponse immédiate et locale), ce cas suppose un contrat de retour plus riche (le résultat peut être silencieux, réclamer une clarification, ou poser une question), une latence et un risque d'échec réseau inédits pour ce mécanisme. Claude a recommandé de ne pas la traiter comme un simple ajout au catalogue existant, mais comme un chantier à part entière justifiant une session dédiée — recommandation implicitement acceptée par Jean-Marc, qui a préféré clore la soirée plutôt que de se lancer dans cette extension à une heure tardive.
**Trois pistes proposées par Jean-Marc pour les prochaines sessions, actées ici pour mémoire :**
1. Concevoir des consignes plus élaborées faisant réellement appel à l'IA (le cas du code postal ci-dessus en étant le premier exemple concret) — préalable : définir le nouveau contrat de retour évoqué plus haut.
2. Commencer à construire un écran de gestion des consignes pour le "boss" (Jean-Marc, puis un futur client), en remplacement de la saisie SQL directe utilisée ce soir — Claude a suggéré de séquencer cet écran **après** la stabilisation du contrat de retour enrichi du point 1, pour ne pas le concevoir deux fois.
3. Réfléchir à un **catalogue restreint et curaté** de consignes et de fonctions avancées, à proposer aux futurs clients de l'agence — Jean-Marc a été explicite sur le fait qu'il n'envisage pas d'ouvrir à ses futurs utilisateurs une création libre de consignes avancées "de A à Z" ; il y voit au contraire un argument commercial pour de futures mises à jour payantes ("Possibilité de nouvelles consignes pour vos assistants"). Ce point rejoint directement la distinction actée plus haut entre mécanisme générique (partagé, neutre) et catalogue de fonctions (à curater soigneusement, puisqu'il devient potentiellement un produit en soi).
**Statut : premier cas concret bâti et validé, philosophie plus large confirmée par la pratique.** Le moteur générique de consignes existe, fonctionne, et a déjà changé de comportement une fois sans code — scellant, à l'échelle d'un cas modeste, l'intuition posée le matin même en section 14. Trois directions ouvertes pour la suite (consignes IA, écran de gestion, catalogue commercial), aucune engagée à ce stade au-delà de la discussion.
@@ -0,0 +1,76 @@
## 30. Chantier des consignes — moteur générique (6 septembre 2026)
**Origine.** Reprise le soir même de la réflexion stratégique de la nuit précédente (Réflexions stratégiques, section 14) sur le pilotage vocal comme deuxième mode de fonctionnement, et de la session de réflexion du matin du 6 septembre sur la distinction entre règles intrinsèques (non modifiables) et consignes personnelles (modifiables par l'utilisateur) — voir Réflexions stratégiques, section 15, pour le fil conceptuel complet. Ce chantier est le premier cas concret construit sur cette distinction, volontairement circonscrit à la fiche contact (création/modification) pour commencer.
**Nouvelle numérotation de version actée en tout début de session.** Format à trois niveaux `V<majeur>.<code>.<conception>` (voir `version.php`, en-tête). Départ de la session : `V1.13.14`. `<code>` et `<conception>` anticipés dès le début de session (Jean-Marc savait déjà que les deux évolueraient) : `V1.14.15`.
**Deux tables créées par Jean-Marc lui-même (convention habituelle) :**
```sql
CREATE TABLE `ASP_Fonctions` (
`FON_Incr` int NOT NULL AUTO_INCREMENT,
`FON_Nom` varchar(30) NOT NULL,
`FON_Nature` varchar(12) NOT NULL,
`FON_Description` varchar(200) NOT NULL,
`FON_ParametresAttendus` varchar(200) NULL,
`FON_NomPHP` varchar(50) NOT NULL,
`FON_Active` varchar(1) NOT NULL DEFAULT 'O',
PRIMARY KEY (`FON_Incr`),
UNIQUE KEY (`FON_Nom`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `ASP_Consignes` (
`CNS_Incr` int NOT NULL AUTO_INCREMENT,
`CNS_Contexte` varchar(40) NOT NULL,
`CNS_Portee` varchar(60) NOT NULL,
`CNS_Champ` varchar(40) NULL,
`CNS_Type` varchar(10) NOT NULL,
`CNS_Nature` varchar(12) NOT NULL,
`CNS_Moment` varchar(15) NULL,
`CNS_Visible` varchar(1) NOT NULL DEFAULT 'O',
`CNS_Modifiable` varchar(1) NOT NULL DEFAULT 'O',
`CNS_Agent` varchar(20) NULL,
`CNS_LibelleCourt` varchar(60) NOT NULL,
`CNS_TexteDefinition` TEXT NOT NULL,
`CNS_TexteAffiche` varchar(300) NOT NULL,
`CNS_Fonction` varchar(30) NULL,
`CNS_Parametres` JSON NULL,
`CNS_MessageErreur` varchar(200) NULL,
`CNS_Active` varchar(1) NOT NULL DEFAULT 'O',
`CNS_DateCreation` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CNS_Utilisateur` varchar(3) NULL,
PRIMARY KEY (`CNS_Incr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
Conventions retenues pour les valeurs de colonnes (choisies par Jean-Marc, différentes du style sans accent utilisé ailleurs comme `CVX_Type`) : `CNS_Type` = `Règle`/`Consigne`, `CNS_Nature` = `Traitement`/`Test`, `CNS_Moment` = `Saisie`/`Validation`, booléens en `O`/`N`. `CNS_Portee` = `'Mise à jour'` est traité comme un mot-clé signifiant "Création et Modification" par le moteur (voir plus bas), à côté de valeurs plus précises possibles plus tard (`'Création'` ou `'Modification'` seules, pour une future règle d'immuabilité). `CNS_Fonction` référence `ASP_Fonctions.FON_Nom` (texte, pas la clé technique `FON_Incr`) — plus lisible pour une inspection directe en base.
**`moteur_consignes.php` — le moteur générique.** Fonction unique `appliquerConsignes(PDO $pdo, string $contexte, string $portee, array &$data): ?string`. Lit `ASP_Consignes` (jointure `ASP_Fonctions` sur `FON_Nom = CNS_Fonction` et `FON_Active='O'`) pour le contexte/portée donnés, applique tous les Traitements actifs (silencieux, mutent `$data` par référence, dans l'ordre de `CNS_Incr`) puis tous les Tests actifs sur les valeurs déjà transformées ; retourne le message du premier test en échec (`CNS_MessageErreur`), ou `null` si tout passe. Aucune référence à un champ ou une consigne précise dans ce fichier — tout vient de la base. Limites assumées pour cette première version (documentées en en-tête du fichier) :
- pas de distinction Saisie/Validation à l'exécution (voir plus bas, décision de Jean-Marc) ;
- les consignes transverses à toute la fiche (`CNS_Champ` NULL, ex. futur contrôle de doublon) sont ignorées, à reprendre le jour d'un vrai cas ;
- un seul message d'erreur renvoyé (le premier test en échec), pas d'accumulation.
**`consignes_fonctions.php` — le catalogue de fonctions.** Bibliothèque sans dépendance (pas de session, pas de base), une fonction par entrée de `ASP_Fonctions.FON_NomPHP`. Convention : un Traitement a la signature `(string $valeur): string`, un Test `(string $valeur): bool`. Trois fonctions écrites ce soir :
- `consigne_majuscule` (`MAJUSCULE`, Traitement) — `mb_strtoupper` avec encodage explicite (un `strtoupper()` simple laisserait les accents français inchangés selon la locale serveur).
- `consigne_non_vide` (`NON_VIDE`, Test) — `trim($valeur) !== ''`.
- `consigne_premiere_lettre_majuscule` (`PREMIERE_LETTRE_MAJUSCULE`, Traitement) — "georges"→"Georges", "JOSEPH"→"Joseph", "jean-marc"→"Jean-Marc". Écrite à la main plutôt qu'avec `ucwords()` : cette fonction native PHP raisonne en octets, pas en caractères, et corromprait un prénom commençant par une lettre accentuée (ex. "Éric"). Implémentation par `preg_replace_callback` avec `\p{L}` (Unicode) après minuscule globale.
**`contacts.php` débarrassé de tout contrôle en dur.** Avant ce soir : `$nom = trim($data['CNT_Nom'] ?? ''); if ($nom === '') { ... 400 ... }` dupliqué en POST et PUT, aucun traitement de mise en forme. Après : un seul appel, `appliquerConsignes($pdo, 'Contacts', 'Création'|'Modification', $data)`, suivi d'un retour 400 avec le message tel quel si non-null. Objectif explicite de Jean-Marc, vérifié en fin de session : ajouter ou modifier une ligne dans `ASP_Consignes`/`ASP_Fonctions` change le comportement réel de l'application sans qu'aucun fichier PHP ne soit retouché, sauf écriture d'une fonction nouvelle ou bug.
**Détour et correction en cours de route : le temps réel, tenté puis abandonné.** Premier réflexe de Claude (avant clarification par Jean-Marc) : coder en dur un contrôle "nom obligatoire" réactif au `blur` du champ côté écran, en plus du contrôle serveur — mauvaise cible, Jean-Marc voulait tester le moteur générique, pas un nouveau code écrit à la main par champ. Après clarification, Jean-Marc a lui-même arbitré l'abandon du temps réel pour cette version (latence non significative pour un seul champ, mais complexité de construction jugée disproportionnée par rapport au gain — priorité donnée à la robustesse). Tout le code de validation côté client (`liste_contacts.html`) a été retiré ; `validerFormulaireContact()` se contente d'envoyer et d'afficher tel quel le message renvoyé par le serveur.
**Bug découvert (et corrigé) précisément à cause de ce retrait.** En enlevant l'ancien contrôle client ("Le nom est obligatoire.", codé en dur avant ce chantier), on a révélé que le gestionnaire d'erreur de `validerFormulaireContact()` affichait déjà, de longue date, un message générique fixe ("Erreur : impossible de créer ce contact.") au lieu du message précis renvoyé par le serveur (`data.error` était lu pour la console, jamais pour le toast). Corrigé dans les trois points d'échec (PUT, POST, catch réseau) : `afficherToast((data && data.error) || <message générique>, true)`.
**Ajustements d'interface demandés par Jean-Marc après premiers tests réels :**
- Toast d'erreur centré sur le panneau de saisie (`top:50%`, comme `#panneau-detail`) et en rouge (`var(--rouge-suppression)`, déjà utilisée pour le mode suppression) — nouvelle classe `.toast-erreur`, activée via un deuxième paramètre de `afficherToast(message, erreur)`. Limité aux erreurs de `validerFormulaireContact` (PUT/POST/catch), les autres toasts du fichier restent inchangés.
- `white-space:pre-line` ajouté à `#toast` (tous les toasts, pas seulement l'erreur) : un `CNS_MessageErreur` contenant des retours à la ligne saisis en base ne s'affichait pas correctement (HTML fond les retours à la ligne en un espace par défaut).
**Test réel réussi en fin de session, sans aucune modification de code :** Jean-Marc a ajouté trois nouvelles lignes dans `ASP_Consignes` (`NON_VIDE` sur `CNT_Prenom` et `CNT_AdrVille`, réutilisant la fonction déjà cataloguée) — comportement immédiatement actif après dépôt des fichiers déjà livrés, aucun fichier PHP/JS retouché. Preuve de concept validée par Jean-Marc : *"On peut dire qu'on n'est pas mauvais !"*
**Reste à faire, noté en fin de session :**
- Concevoir un nouveau type de consigne pour les cas nécessitant un appel IA en cours de traitement (ex. retrouver le code postal d'une ville saisie sans code postal) — contrat de retour plus riche que Traitement/Test actuel (silencieux / besoin de précision / question à poser), gestion d'échec réseau, coût et latence d'un appel externe. Session dédiée à prévoir, pas un ajout mineur au catalogue existant.
- Écran de gestion des consignes par le boss (au lieu de `INSERT`/`UPDATE` SQL à la main) — à construire après stabilisation du contrat ci-dessus, pour ne pas le refaire.
- Réflexion sur un catalogue restreint de consignes/fonctions avancées à proposer aux futurs utilisateurs de l'agence, sans leur ouvrir la création libre de A à Z — argument commercial identifié par Jean-Marc ("Possibilité de nouvelles consignes pour vos assistants").
- Accumulation de plusieurs messages d'erreur à la fois (le moteur ne remonte aujourd'hui que le premier test en échec).
- Gestion des consignes transverses à toute la fiche (`CNS_Champ` NULL, ex. contrôle de doublon) — non traitée par le moteur actuel.
- Distinction Saisie/Validation réellement exploitée un jour (temps réel), si le besoin redevient assez fort pour justifier la complexité écartée ce soir.
+30 -11
View File
@@ -694,6 +694,21 @@
padding:14px 24px;border-radius:6px;font-size:14px; padding:14px 24px;border-radius:6px;font-size:14px;
box-shadow:0 6px 20px rgba(0,0,0,0.3); box-shadow:0 6px 20px rgba(0,0,0,0.3);
display:none;z-index:700; display:none;z-index:700;
/* Un CNS_MessageErreur (ou tout autre texte affiché ici) peut contenir
des retours à la ligne saisis en base — pre-line les restitue tels
quels au lieu de les fondre en un seul espace (comportement HTML par
défaut), tout en continuant à retourner à la ligne normalement si le
texte est trop long. Concerne tous les toasts, pas seulement l'erreur :
aucune raison qu'un message informatif se comporte différemment. */
white-space:pre-line;max-width:420px;text-align:center;
}
/* Variante erreur (6 septembre 2026, chantier des consignes) : un échec de
consigne au moment d'enregistrer une fiche contact doit sauter aux yeux,
centré sur le panneau de saisie plutôt qu'en bas de l'écran — voir
afficherToast(message, erreur) et validerFormulaireContact(). */
#toast.toast-erreur{
top:50%;bottom:auto;transform:translate(-50%,-50%);
background:var(--rouge-suppression);color:#fff;
} }
</style> </style>
</head> </head>
@@ -1403,9 +1418,12 @@ document.getElementById("menu-filtrer").addEventListener("click", ()=>{
// ---- Toast ---- // ---- Toast ----
let toastTimer = null; let toastTimer = null;
function afficherToast(message){ function afficherToast(message, erreur){
const toast = document.getElementById("toast"); const toast = document.getElementById("toast");
toast.textContent = message; toast.textContent = message;
// Variante erreur (6 septembre 2026) : centrée sur le panneau de saisie,
// en rouge — voir usage dans validerFormulaireContact().
toast.classList.toggle("toast-erreur", !!erreur);
toast.style.display = "block"; toast.style.display = "block";
clearTimeout(toastTimer); clearTimeout(toastTimer);
toastTimer = setTimeout(()=>{ toast.style.display = "none"; }, 2600); toastTimer = setTimeout(()=>{ toast.style.display = "none"; }, 2600);
@@ -2206,13 +2224,12 @@ async function lireReponse(reponse){
// réellement la création (génération de CNT_Code désormais implémentée côté // réellement la création (génération de CNT_Code désormais implémentée côté
// contacts.php, REA_CodeUser lu depuis la session, voir journal section 16). // contacts.php, REA_CodeUser lu depuis la session, voir journal section 16).
async function validerFormulaireContact(){ async function validerFormulaireContact(){
const nomInput = panneauDetail.querySelector('input.champ-edit[data-champ="CNT_Nom"]'); // Plus aucun contrôle écrit en dur ici (chantier des consignes, 6 septembre
if(!nomInput.value.trim()){ // 2026, voir Réflexions stratégiques et moteur_consignes.php) : c'est
afficherToast("Le nom est obligatoire."); // contacts.php (POST/PUT) qui applique les consignes actives et renvoie,
nomInput.focus(); // le cas échéant, le message d'erreur tel quel — affiché ci-dessous sans
return; // savoir à l'avance de quelle consigne il vient. Ajouter une consigne côté
} // base ne demande donc plus aucune modification ici.
const donnees = {}; const donnees = {};
CHAMPS_TEXTE_MODIFIABLES.forEach(nomChamp=>{ CHAMPS_TEXTE_MODIFIABLES.forEach(nomChamp=>{
const el = panneauDetail.querySelector( const el = panneauDetail.querySelector(
@@ -2239,7 +2256,9 @@ async function validerFormulaireContact(){
const {json: data, texte} = await lireReponse(reponse); const {json: data, texte} = await lireReponse(reponse);
if(!reponse.ok || (data && data.error)){ if(!reponse.ok || (data && data.error)){
console.error("Erreur PUT contacts.php :", reponse.status, data ? data.error : texte); console.error("Erreur PUT contacts.php :", reponse.status, data ? data.error : texte);
afficherToast("Erreur : impossible d'enregistrer les modifications."); // Message précis du serveur (ex. une consigne en échec) si présent,
// message générique seulement en dernier recours (ex. panne réseau).
afficherToast((data && data.error) || "Erreur : impossible d'enregistrer les modifications.", true);
return; return;
} }
} else { } else {
@@ -2251,7 +2270,7 @@ async function validerFormulaireContact(){
const {json: data, texte} = await lireReponse(reponse); const {json: data, texte} = await lireReponse(reponse);
if(!reponse.ok || !data || data.error || !data.id){ if(!reponse.ok || !data || data.error || !data.id){
console.error("Erreur POST contacts.php :", reponse.status, data ? data.error : texte); console.error("Erreur POST contacts.php :", reponse.status, data ? data.error : texte);
afficherToast("Erreur : impossible de créer ce contact."); afficherToast((data && data.error) || "Erreur : impossible de créer ce contact.", true);
return; return;
} }
idContact = data.id; idContact = data.id;
@@ -2292,7 +2311,7 @@ async function validerFormulaireContact(){
console.error("Erreur d'enregistrement :", erreur); console.error("Erreur d'enregistrement :", erreur);
afficherToast(enModification afficherToast(enModification
? "Erreur : impossible d'enregistrer les modifications." ? "Erreur : impossible d'enregistrer les modifications."
: "Erreur : impossible de créer ce contact."); : "Erreur : impossible de créer ce contact.", true);
} }
} }
+80
View File
@@ -0,0 +1,80 @@
<?php
/**
* ASPIC — moteur_consignes.php
*
* Moteur générique du chantier des consignes (6 septembre 2026, voir
* Réflexions stratégiques). Lit ASP_Consignes/ASP_Fonctions à l'exécution et
* applique ce qu'il y trouve — l'objectif explicite de Jean-Marc étant que
* l'ajout d'une ligne dans ASP_Consignes change le comportement réel de
* l'application sans qu'aucun fichier PHP/JS ne soit retouché, sauf si une
* fonction nouvelle doit être écrite dans consignes_fonctions.php (ou en cas
* de bug). Ce fichier ne doit donc plus jamais contenir la moindre référence
* à un champ ou une consigne précis — tout vient de la base.
*
* Portée de cette première version, volontairement limitée :
* - Un seul champ à la fois (CNS_Champ NULL — consigne transverse à la fiche
* entière, ex. futur contrôle de doublon — n'est PAS encore géré ; ces
* lignes sont ignorées pour l'instant, à reprendre quand un vrai cas se
* présentera).
* - CNS_Moment n'est PAS distingué ici : temps réel (Saisie) abandonné pour
* cette version (décision de Jean-Marc, 6 septembre 2026 — priorité à la
* robustesse plutôt qu'au retour immédiat à l'écran). Toute consigne
* active s'applique donc au moment de l'enregistrement, qu'elle porte
* Saisie ou Validation.
* - Un seul message d'erreur retourné (le premier test en échec) — pas
* d'accumulation de plusieurs erreurs à la fois pour ce premier chantier.
*
* CNS_Portee : 'Mise à jour' est traité comme un mot-clé signifiant "les deux
* moments" (Création et Modification), à côté de valeurs plus précises
* ('Création' ou 'Modification' seules seraient possibles plus tard pour une
* règle d'immuabilité — voir Réflexions stratégiques).
*/
/**
* @param PDO $pdo Connexion déjà ouverte (voir db.php).
* @param string $contexte Ex. 'Contacts' (ASP_Consignes.CNS_Contexte).
* @param string $portee 'Création' ou 'Modification'.
* @param array $data Données du formulaire, par référence — les
* traitements silencieux (ex. majuscules) modifient
* directement ce tableau.
* @return string|null Message d'erreur du premier test en échec (déjà le
* texte à renvoyer tel quel au front-end), ou null
* si tout est passé.
*/
function appliquerConsignes(PDO $pdo, string $contexte, string $portee, array &$data): ?string
{
$sql = "SELECT c.CNS_Champ, c.CNS_Nature, c.CNS_MessageErreur, f.FON_NomPHP
FROM ASP_Consignes c
LEFT JOIN ASP_Fonctions f
ON f.FON_Nom = c.CNS_Fonction AND f.FON_Active = 'O'
WHERE c.CNS_Contexte = ?
AND c.CNS_Active = 'O'
AND c.CNS_Champ IS NOT NULL
AND (c.CNS_Portee = 'Mise à jour' OR FIND_IN_SET(?, c.CNS_Portee))
ORDER BY c.CNS_Incr";
$stmt = $pdo->prepare($sql);
$stmt->execute([$contexte, $portee]);
$consignes = $stmt->fetchAll();
// Traitements d'abord (silencieux, jamais de message) : une consigne de
// test lue ensuite doit voir la valeur déjà mise en forme (ex. le nom
// déjà en majuscules avant de vérifier qu'il n'est pas vide).
foreach ($consignes as $c) {
if ($c['CNS_Nature'] !== 'Traitement') continue;
if (!$c['FON_NomPHP'] || !function_exists($c['FON_NomPHP'])) continue;
$valeurActuelle = (string)($data[$c['CNS_Champ']] ?? '');
$data[$c['CNS_Champ']] = call_user_func($c['FON_NomPHP'], $valeurActuelle);
}
// Puis les tests, sur les valeurs déjà transformées ci-dessus.
foreach ($consignes as $c) {
if ($c['CNS_Nature'] !== 'Test') continue;
if (!$c['FON_NomPHP'] || !function_exists($c['FON_NomPHP'])) continue;
$valeur = (string)($data[$c['CNS_Champ']] ?? '');
if (!call_user_func($c['FON_NomPHP'], $valeur)) {
return $c['CNS_MessageErreur'] ?: 'Consigne non respectée.';
}
}
return null;
}
+1 -1
View File
@@ -50,7 +50,7 @@
* capacité à détecter un déploiement resté en retard. * capacité à détecter un déploiement resté en retard.
*/ */
const ASPIC_VERSION = 'V1.13.14'; const ASPIC_VERSION = 'V1.14.15';
if (basename($_SERVER['SCRIPT_NAME']) === 'version.php') { if (basename($_SERVER['SCRIPT_NAME']) === 'version.php') {
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');