L'application WinDev Production Table TL21 est conçue pour gérer l'édition simultanée multi-utilisateurs d'une table de production avec synchronisation temps réel.
┌─────────────────────────────────────────────────────────────────────┐
│ COUCHE PRÉSENTATION │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ TABLE_Prod_TL21 (26 colonnes) │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ COL_ │ │ COL_ │ │ COL_ │ │ COL_ │ ... │ │
│ │ │ Client │ │ Affaire │ │ Commande│ │ PIECE │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ Événements: │ │
│ │ • Entrée dans colonne → VerrouillerLignePourSaisie() │ │
│ │ • Sortie de colonne → gbSaisieEnCours = Faux │ │
│ │ • Sortie de ligne → EnregistrerLigneModifiee() │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Boutons de Contrôle │ │
│ │ │ │
│ │ [BTN_UP] [BTN_DOWN] [BTN_Ajout] │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ COUCHE MÉTIER │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Procédures Métier │ │
│ │ │ │
│ │ • EnregistrerLigneModifiee() │ │
│ │ • VerrouillerLignePourSaisie() │ │
│ │ • MemoriserPositionSaisie() │ │
│ │ • RestaurationPositionSaisie() │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Variables Globales │ │
│ │ │ │
│ │ • gbModificationParMoiMeme │ │
│ │ • gbSaisieEnCours │ │
│ │ • gbActualisationEnAttente │ │
│ │ • gnNombreModifications │ │
│ │ • gsUtilisateurActuel │ │
│ │ • gnIDLigneEnCoursDeModification │ │
│ │ • gnLigneEnCoursDeSaisie │ │
│ │ • gnColonneEnCoursDeSaisie │ │
│ │ • gsContenuCelluleEnCours │ │
│ │ • gnIDLigneEnCoursDeSaisie │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ COUCHE SYNCHRONISATION │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ HSurveille_Callback() │ │
│ │ │ │
│ │ SI gbModificationParMoiMeme ALORS RETOUR │ │
│ │ │ │
│ │ SI gbSaisieEnCours ALORS │ │
│ │ → MemoriserPositionSaisie() │ │
│ │ → TableAffiche(taRéExécuteRequete) │ │
│ │ → RestaurationPositionSaisie() │ │
│ │ SINON │ │
│ │ → gbActualisationEnAttente = Vrai │ │
│ │ FIN │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ COUCHE DONNÉES │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Base HFSQL │ │
│ │ │ │
│ │ Table: Prod_TL21 │ │
│ │ • 29 champs │ │
│ │ • Clé primaire: IDProd_TL21 │ │
│ │ • Champ de verrouillage: Modifie_par │ │
│ │ │ │
│ │ Fonctions HFSQL: │ │
│ │ • HLitRecherchePremier() │ │
│ │ • HModifie() │ │
│ │ • HBloqueNumEnr() │ │
│ │ • HDébloqueNumEnr() │ │
│ │ • HSurveille() │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
User clique sur cellule
│
▼
Événement "Entrée dans COL_xxx"
│
▼
VerrouillerLignePourSaisie()
│
├─→ gbSaisieEnCours = Vrai
│
├─→ HLitRecherchePremier(Prod_TL21, IDProd_TL21, nID)
│
├─→ SI Modifie_par = "" OU Modifie_par = gsUtilisateurActuel
│ │
│ ├─→ HBloqueNumEnr(Prod_TL21, hNumEnrEnCours)
│ │
│ ├─→ gbModificationParMoiMeme = Vrai
│ │
│ ├─→ Prod_TL21.Modifie_par = gsUtilisateurActuel
│ │
│ ├─→ HModifie(Prod_TL21)
│ │
│ └─→ gbModificationParMoiMeme = Faux
│
└─→ SINON
│
└─→ ToastAffiche("⚠️ Ligne en cours d'édition par [utilisateur]")
User quitte cellule
│
▼
Événement "Sortie de COL_xxx"
│
└─→ gbSaisieEnCours = Faux
User quitte ligne
│
▼
Événement "Sortie de saisie d'une ligne"
│
├─→ EnregistrerLigneModifiee()
│ │
│ ├─→ HLitRecherchePremier(Prod_TL21, IDProd_TL21, nID)
│ │
│ ├─→ gbModificationParMoiMeme = Vrai
│ │
│ ├─→ Prod_TL21.Client = TABLE_Prod_TL21.COL_Client
│ │ Prod_TL21.Affaire = TABLE_Prod_TL21.COL_Affaire
│ │ ... (29 champs)
│ │
│ ├─→ HModifie(Prod_TL21)
│ │
│ └─→ gbModificationParMoiMeme = Faux
│
├─→ HDébloqueNumEnr(Prod_TL21, hNumEnrEnCours)
│
├─→ Prod_TL21.Modifie_par = ""
│
└─→ SI gbActualisationEnAttente ALORS
│
├─→ TableAffiche(TABLE_Prod_TL21, taRéExécuteRequete)
│
└─→ gbActualisationEnAttente = Faux
User A enregistre modification
│
▼
HSurveille détecte changement
│
▼
HSurveille_Callback() appelé chez User B
│
├─→ SI gbModificationParMoiMeme = Vrai
│ │
│ └─→ RETOUR (ignorer)
│
└─→ SI gbSaisieEnCours = Vrai
│
├─→ MemoriserPositionSaisie()
│ │
│ ├─→ gnLigneEnCoursDeSaisie = TableSelect(TABLE_Prod_TL21)
│ │
│ ├─→ gnIDLigneEnCoursDeSaisie = TABLE_Prod_TL21.COL_ID
│ │
│ ├─→ gnColonneEnCoursDeSaisie = TableSelectOccurrence(..., tscColonne)
│ │
│ └─→ gsContenuCelluleEnCours = TABLE_Prod_TL21.COL_xxx
│
├─→ TableAffiche(TABLE_Prod_TL21, taRéExécuteRequete)
│
└─→ RestaurationPositionSaisie()
│
├─→ Rechercher ligne par ID
│
├─→ TABLE_Prod_TL21.COL_xxx[nLigne] = gsContenuCelluleEnCours
│
├─→ TableSelectPlus(TABLE_Prod_TL21, nLigne, nColonne)
│
└─→ gbSaisieEnCours = Vrai
L'application utilise un double niveau de verrouillage pour assurer l'intégrité des données :
HBloqueNumEnr(Prod_TL21, hNumEnrEnCours)
Avantages :
- ✅ Empêche les modifications concurrentes au niveau de la base
- ✅ Protège contre les conflits d'écriture
Libération :
HDébloqueNumEnr(Prod_TL21, hNumEnrEnCours)
Prod_TL21.Modifie_par = gsUtilisateurActuel
Avantages :
- ✅ Indicateur visuel pour les autres utilisateurs
- ✅ Permet de savoir qui édite la ligne
- ✅ Peut être affiché dans l'interface
Libération :
Prod_TL21.Modifie_par = ""
| Aspect | Verrouillage HFSQL | Verrouillage Application |
|---|---|---|
| Niveau | Base de données | Interface utilisateur |
| Protection | Conflits d'écriture | Indication visuelle |
| Visibilité | Interne | Visible par tous |
| Libération | Automatique ou manuelle | Manuelle uniquement |
| Utilisation | Technique | UX/UI |
| Variable | Type | Description | Utilisation |
|---|---|---|---|
gbModificationParMoiMeme |
Booléen | Indique si la modification vient de l'utilisateur actuel | Empêche HSurveille de se déclencher sur ses propres modifications |
gbSaisieEnCours |
Booléen | Indique si l'utilisateur est en train de saisir | Empêche le rafraîchissement pendant la saisie |
gbActualisationEnAttente |
Booléen | Indique qu'un rafraîchissement est en attente | Applique le rafraîchissement après la saisie |
gnNombreModifications |
Entier | Compteur de modifications détectées | Affichage dans les toasts |
gsUtilisateurActuel |
Chaîne | Nom de l'utilisateur actuel | Verrouillage et identification |
gnIDLigneEnCoursDeModification |
Entier | ID de la ligne verrouillée | Libération du verrou HFSQL |
gnLigneEnCoursDeSaisie |
Entier | Numéro de ligne en cours de saisie | Restauration de position |
gnColonneEnCoursDeSaisie |
Entier | Numéro de colonne en cours de saisie | Restauration de position |
gsContenuCelluleEnCours |
Chaîne | Contenu de la cellule en cours | Restauration du contenu |
gnIDLigneEnCoursDeSaisie |
Entier | ID de la ligne en cours de saisie | Recherche après rafraîchissement |
Rôle : Sauvegarde tous les champs de la ligne modifiée dans la base de données.
Paramètres : Aucun (utilise la ligne sélectionnée dans la table)
Retour : Aucun
Encapsulation : Oui (gbModificationParMoiMeme = Vrai)
Champs sauvegardés : 29 champs (voir DATABASE_SCHEMA.md)
Rôle : Verrouille l'enregistrement pour édition exclusive.
Paramètres : Aucun (utilise la ligne sélectionnée dans la table)
Retour : Aucun
Actions :
- Active
gbSaisieEnCours - Lit l'enregistrement
- Vérifie si disponible
- Bloque avec
HBloqueNumEnr() - Définit
Modifie_par - Affiche un toast si déjà verrouillé
Rôle : Mémorise la position et le contenu avant rafraîchissement.
Paramètres : Aucun
Retour : Aucun
Mémorise :
- Numéro de ligne
- Numéro de colonne
- ID de l'enregistrement
- Contenu de la cellule
Rôle : Restaure la position et le contenu après rafraîchissement.
Paramètres : Aucun
Retour : Aucun
Restaure :
- Recherche la ligne par ID
- Restaure le contenu de la cellule
- Replace le focus dans la cellule
- Réactive
gbSaisieEnCours
Déclencheur : User clique dans une cellule
Action : Appelle VerrouillerLignePourSaisie()
Fréquence : À chaque entrée dans une cellule éditable (26 colonnes)
Déclencheur : User quitte une cellule
Action : Désactive gbSaisieEnCours
Fréquence : À chaque sortie d'une cellule éditable (26 colonnes)
Déclencheur : User quitte une ligne
Actions :
- Enregistre la ligne
- Libère le verrou HFSQL
- Libère le champ
Modifie_par - Applique l'actualisation en attente si nécessaire
Fréquence : Une fois par ligne éditée
Problème : HSurveille se déclenche sur les propres modifications de l'utilisateur.
Solution : Utiliser un flag gbModificationParMoiMeme pour encapsuler les modifications.
gbModificationParMoiMeme = Vrai
// Modification
gbModificationParMoiMeme = Faux
Problème : Le rafraîchissement interrompt la saisie de l'utilisateur.
Solution : Différer l'actualisation jusqu'à la fin de la saisie.
SI gbSaisieEnCours = Vrai ALORS
gbActualisationEnAttente = Vrai
SINON
TableAffiche(...)
FIN
Problème : Le rafraîchissement fait perdre la position et le contenu.
Solution : Mémoriser l'état avant rafraîchissement et le restaurer après.
MemoriserPositionSaisie()
TableAffiche(...)
RestaurationPositionSaisie()
Problème : Les utilisateurs ne voient pas les modifications des autres.
Solution : HSurveille notifie tous les utilisateurs des changements.
HSurveille(Prod_TL21, HSurveille_Callback)
┌─────────────────────────────────────────────────────────────────┐
│ User A │
└─────────────────────────────────────────────────────────────────┘
│
│ 1. Clique sur cellule
▼
┌─────────────────────────────────────────────────────────────────┐
│ Entrée dans COL_Client │
│ → VerrouillerLignePourSaisie() │
│ → gbSaisieEnCours = Vrai │
│ → HBloqueNumEnr() │
│ → Modifie_par = "User A" │
└─────────────────────────────────────────────────────────────────┘
│
│ 2. Saisit "Nouveau Client"
▼
┌─────────────────────────────────────────────────────────────────┐
│ Sortie de COL_Client │
│ → gbSaisieEnCours = Faux │
└─────────────────────────────────────────────────────────────────┘
│
│ 3. Quitte la ligne
▼
┌─────────────────────────────────────────────────────────────────┐
│ Sortie de saisie d'une ligne │
│ → EnregistrerLigneModifiee() │
│ → gbModificationParMoiMeme = Vrai │
│ → Prod_TL21.Client = "Nouveau Client" │
│ → HModifie(Prod_TL21) │
│ → gbModificationParMoiMeme = Faux │
│ → HDébloqueNumEnr() │
│ → Modifie_par = "" │
└─────────────────────────────────────────────────────────────────┘
│
│ 4. HSurveille détecte changement
▼
┌─────────────────────────────────────────────────────────────────┐
│ User B │
└─────────────────────────────────────────────────────────────────┘
│
│ 5. HSurveille_Callback() appelé
▼
┌─────────────────────────────────────────────────────────────────┐
│ HSurveille_Callback() │
│ → SI gbModificationParMoiMeme = Faux │
│ → SI gbSaisieEnCours = Vrai │
│ → MemoriserPositionSaisie() │
│ → TableAffiche(taRéExécuteRequete) │
│ → RestaurationPositionSaisie() │
│ SINON │
│ → gbActualisationEnAttente = Vrai │
└─────────────────────────────────────────────────────────────────┘
│
│ 6. User B voit "Nouveau Client"
▼
┌─────────────────────────────────────────────────────────────────┐
│ Table rafraîchie avec nouvelles données │
│ User B reste dans sa cellule si en saisie │
└─────────────────────────────────────────────────────────────────┘
- Actualisation conditionnelle : Ne rafraîchit que si nécessaire
- Encapsulation des modifications : Évite les callbacks inutiles
- Verrouillage ciblé : Verrouille uniquement l'enregistrement en cours
- Mémorisation minimale : Mémorise uniquement les données nécessaires
| Opération | Temps Estimé | Impact Utilisateur |
|---|---|---|
| Verrouillage | < 50ms | Imperceptible |
| Enregistrement | < 100ms | Très faible |
| Rafraîchissement | < 200ms | Faible |
| Restauration | < 50ms | Imperceptible |
- Verrouillage optimiste : Vérifie avant de verrouiller
- Toast d'avertissement : Informe si ligne déjà verrouillée
- Libération automatique : Libère à la sortie de ligne
- Double vérification : HFSQL + Application
- Transaction implicite : HModifie() est atomique
- Encapsulation : Évite les modifications concurrentes
- Validation : Vérifie l'utilisateur avant libération
- Test mono-utilisateur : Vérifier l'enregistrement basique
- Test bi-utilisateur : Vérifier le verrouillage et la synchronisation
- Test multi-utilisateur : Vérifier la scalabilité (3+ utilisateurs)
- Test de conflit : Deux utilisateurs tentent d'éditer la même ligne
- Test de déconnexion : Vérifier la libération des verrous
- ✅ Pas de perte de données
- ✅ Pas d'interruption de saisie
- ✅ Synchronisation temps réel
- ✅ Indicateurs visuels corrects
- ✅ Performance acceptable
Dernière mise à jour : 2025-01-04