Introduction
Un filtre est appliqué directement sur une requête SELECT
, dans un Charger()
d’une classe de type MObjetListe
.
Il permet de créer la partie de code de la clause WHERE
.
Mise en place d’un filtre sur un MObjetListe
- Créer un
MObjetFiltre
. - Il hérite de
AFiltreListe
. - Les membres sont les paramètres du filtre.
Déclaration
STCaracteristiqueReference est une Structure
nRefCaracteristique est un entier
nRefValeur est un entier
FIN
STCaracteristiqueNumerique est une Structure
nRefCaracteristique est un entier
rMin est un réel
rMax est un réel
FIN
MArticleFiltre est une Classe
hérite de AFiltreListe
PUBLIC
m_tabEtat est un tableau d'entiers
m_tabRefFamille est un tableau d'entiers
m_tabRefArticle est un tableau d'entiers
m_tabCodeArticle est un tableau de chaînes
m_tabArticleGenerique est un tableau de chaînes
PRIVÉ
m_tabCaracteristiqueReference est un tableau de STCaracteristiqueReference
m_tabCaracteristiqueNumerique est un tableau de STCaracteristiqueNumerique
FIN
Constructeur
Ne pas oublier de définir si le filtre vide est autorisé.
Par défaut, le filtre vide n’est pas autorisé.
Pour l’autoriser, il faut : m_bFiltreVideAutorise = Vrai
.
Redéfinition de p_sNomComplet
(ABSTRAIT)
Liste des noms de paramètres compréhensibles pour l’utilisateur, séparés par un point-virgule.
PROCÉDURE p_sNomComplet() : chaîne
sNom est une chaîne
sNom += [RC] + "Nom : [%m_sNom%]"
sNom += [RC] + "Etat : [%m_tabEtat..Occurrence%]"
sNom += [RC] + "Famille : [%m_tabRefFamille..Occurrence%]"
sNom += [RC] + "Article : [%m_tabRefArticle..Occurrence%]"
sNom += [RC] + "Code article : [%m_tabCodeArticle..Occurrence%]"
sNom += [RC] + "Article générique : [%m_tabArticleGenerique..Occurrence%]"
sNom += [RC] + "Caractéristique (référence) : [%m_tabCaracteristiqueReference..Occurrence%]"
sNom += [RC] + "Caractéristique (numérique) : [%m_tabCaracteristiqueNumerique..Occurrence%]"
RENVOYER sNom
Redéfinition de p_bFiltreVide
(ABSTRAIT)
- RENVOYER
Faux
si aucun paramètre du filtre n’est utilisé. - RENVOYER
Vrai
si au moins un paramètre est utilisé.
Si l’état de l’objet (ACTIF, INACTIF, SUPPRIME) est un membre du filtre. Il ne faut pas en tenir compte dans p_bFiltreVide
.
Autrement dit, même si m_tabRefEtat..Vide = faux
, le filtre peut être considéré comme vide.
PROCÉDURE PUBLIQUE p_bFiltreVide() : booléen
SI PAS m_sNom ~= "" ALORS RENVOYER Faux
//si pas m_tabEtat..vide alors renvoyer faux
SI PAS m_tabRefFamille..Vide RENVOYER Faux
SI PAS m_tabRefArticle..Vide RENVOYER Faux
SI PAS m_tabCodeArticle..Vide ALORS RENVOYER Faux
SI PAS m_tabArticleGenerique..Vide ALORS RENVOYER Faux
SI PAS m_tabCaracteristiqueReference..Vide ALORS RENVOYER Faux
SI PAS m_tabCaracteristiqueNumerique..Vide ALORS RENVOYER Faux
RENVOYER Vrai
Procédure ResetFiltre()
Tous les paramètres sont vidés.
Ne pas prendre l’exemple ci-dessous pour argent comptant ! Il faut peut-être vider les paramètres un par un.
PROCÉDURE ResetFiltre()
VariableRAZ(m_sNom)
m_tabEtat = [CEtat.REF_ACTIF]
m_tabRefFamille.SupprimeTout()
m_tabRefArticle.SupprimeTout()
m_tabCodeArticle.SupprimeTout()
m_tabArticleGenerique.SupprimeTout()
m_tabCaracteristiqueReference.SupprimeTout()
m_tabCaracteristiqueNumerique.SupprimeTout()
Procédure GetFiltre()
Créer le code SQL à joindre à la clause WHERE
.
RENVOYER sFiltre
en fin de code.
Il n’y a pas de WHERE
dans un GetFiltre()
; il est mentionné dans la requête principale.
Cela permet de commencer chaque élément de requête par un AND
.
Si aucun WHERE
n’est requis, il suffit d’en créer un avec une condition toujours vraie.
PROCÉDURE GetFiltre() : chaîne
sFiltre est une chaîne
tabTMP est un tableau de chaînes
sTMP est une chaîne
nJointure est un entier
SI PAS m_tabEtat..Vide ALORS
sTMP = TableauEntierVersChaineAvecBalise(m_tabEtat,",","")
sFiltre += [RC] + "AND ar.actif IN ([%sTMP%])"
FIN
SI PAS m_sNom ~= "" ALORS
ChaîneVersTableau(m_sNom,tabTMP,RC)
POUR TOUT sTMP DE tabTMP
sFiltre += [RC] + "AND ar.nom LIKE '%[%SQLDoubleQuote(sTMP)%]%'"
FIN
FIN
SI PAS m_tabRefFamille..Vide ALORS
sTMP = TableauEntierVersChaineAvecBalise(m_tabRefFamille,",","")
sFiltre += [RC] + "AND ar.ref_famille IN ([%sTMP%])"
FIN
SI PAS m_tabRefArticle..Vide ALORS
sTMP = TableauEntierVersChaineAvecBalise(m_tabRefArticle,",","")
sFiltre += [RC] + "AND ar.ref_article IN ([%sTMP%])"
FIN
SI PAS m_tabCodeArticle..Vide ALORS
sTMP = TableauChaineVersChaineAvecBalise(m_tabCodeArticle,",","'")
sFiltre += [RC] + "AND ar.code IN ([%sTMP%])"
FIN
SI PAS m_tabArticleGenerique..Vide ALORS
sTMP = TableauChaineVersChaineAvecBalise(m_tabArticleGenerique,",","'")
sFiltre += [RC] + "AND fa.article_generique IN ([%sTMP%])"
FIN
POUR TOUT M DE m_tabCaracteristiqueReference
nJointure++
sFiltre += [RC] + "AND lk[%nJointure%].ref_valeur = [%M.nRefValeur%]"
FIN
POUR TOUT M DE m_tabCaracteristiqueNumerique
nJointure++
SI M.rMin <> 0 _ET_ M.rMax <> 0 ALORS
// BETWEEN CONSIDERE INCLUSIF
sFiltre += [RC] + "AND CAST(lk[%nJointure%].valeur AS float) BETWEEN [%M.rMin%] AND [%M.rMax%]"
SINON SI M.rMin <> 0 _ET_ M.rMax = 0 ALORS
sFiltre += [RC] + "AND CAST(lk[%nJointure%].valeur AS float) >= [%M.rMin%]"
SINON SI M.rMin = 0 _ET_ M.rMax <> 0 ALORS
sFiltre += [RC] + "AND CAST(lk[%nJointure%].valeur AS float) <= [%M.rMax%]"
FIN
FIN
RENVOYER sFiltre
Inclusion du filtre dans l’objet MObjetListe
m_pclFiltre
est unMObjetFiltre
dynamique, etc.- Créer une propriété en lecture/écriture
p_pclFiltre
. - Garder
m_pclFiltre
enPUBLIC
. - Inclure le filtre dans la requête
SELECT
.
Modification des jointures par le filtre
Quand le filtre requiert une modification des jointures, il faut ajouter une méthode GetJointure()
qui renvoie cette partie de requête.
PROCÉDURE GetJointure() : chaîne
sJointure est une chaîne
nJointure est un entier
POUR TOUT M DE m_tabCaracteristiqueReference
nJointure++
sJointure += [RC] + [
INNER JOIN LATEArticle_LKArticleCaracteristique AS lk[%nJointure%] ON lk[%nJointure%].ref_article = ar.ref_article AND lk[%nJointure%].ref_caracteristique = [%M.nRefCaracteristique%]
]
FIN
POUR TOUT M DE m_tabCaracteristiqueNumerique
nJointure++
sJointure += [RC] + [
INNER JOIN LATEArticle_LKArticleCaracteristique AS lk[%nJointure%] ON lk[%nJointure%].ref_article = ar.ref_article
AND lk[%nJointure%].ref_caracteristique = [%M.nRefCaracteristique%]
AND ISNUMERIC(lk[%nJointure%].valeur) = 1
AND lk[%nJointure%].valeur <> '-'
]
FIN
RENVOYER sJointure
Modification (22/04/25) : m_bFiltreVideAutorise
Remplacement de m_bFiltreVideAutorise
par m_bFiltreVideInterdit
Contexte
La classe AFiltreListe
a été introduite pour centraliser la logique de validation des filtres dans l'application. À l'origine, un booléen nommé m_bFiltreVideAutorise
contrôlait si un filtre vide était autorisé.
Cependant, la valeur par défaut était Faux
, interdisant les filtres vides, ce qui allait à l'encontre de la logique métier utilisée dans 95% des cas, où les filtres vides sont en réalité permis.
Problème rencontré
Cette inversion logique a provoqué des erreurs de compréhension et des erreurs d'exécution, notamment dans le module LambiqueWelding
où une opération a mis à jour l'intégralité d'une table en base de données, faute d'un filtre actif mais considéré valide par erreur.
Solution apportée
- Remplacement du champ
m_bFiltreVideAutorise
parm_bFiltreVideInterdit
. - Valeur par défaut :
Faux
(donc les filtres vides sont autorisés par défaut). - Réécriture de la méthode
p_bValide()
pour refléter cette logique :
SI p_bFiltreVide _ET_ m_bFiltreVideInterdit ALORS
Erreur("Filtre vide non autorisé...")
RENVOYER Faux
FIN
RENVOYER Vrai
Avantages du changement
- La logique par défaut est maintenant en phase avec l'usage réel.
- Réduction du risque d'erreurs silencieuses dans les modules métiers.
- Amélioration de la lisibilité et de la compréhension du code.
- Facilite l'intégration de nouveaux développeurs grâce à une logique plus naturelle.
Prochaines étapes recommandées
- Audit du code pour remplacer toutes les anciennes références à
m_bFiltreVideAutorise
. - Ajout de vérifications systématiques avec
p_bValide()
avant toute opération en base. - Ajout de logs si
p_bFiltreVide() = Vrai
, à des fins de contrôle qualité.