Projet e-stock  0.2
BTS SNIR LaSalle Avignon 2020
Pierre-Antoine Legger

Présentation personnelle

Rappel du besoin initial

Le système e-stock est un système de gestion permettant la de contrôler et gérer l'utilisation de produits stockés dans une armoire et d'en assurer là traçabilité de l'attribution du matériel et des consommables stockés. Puis de sécuriser l'accès par un contrôle d'accès par badge RFID.

Le lecteur OMNIKEY

Le lecteur OMNIKEY® 5427CK de HID Global fonctionne dans tous les environnements PC. Indépendant du système d'exploitation et du cas d'usage, les modes CCID ou l'interface Keyboard Wedge fournit la solution idéale sans installer ou assurer des drivers. Cela supprime les problèmes complexes de gestion du cycle de vie du logiciel sur site.

omnikeyr-5427-ck.png
OMNIKEY 5427 CK

La fonctionnalité d'émulation clavier soit en "QWERTY" ou en "QWERTZ", permet de récupérer et de traiter des données de la carte pour les entrer directement dans les applications en émulant sur la séquence de touches clavier correspondante.

Exemple de trame de badge lu : RFID:30DDA983

  • "RFID:" indique le début la trame
  • "30DDA983" est l'UID du badge RFID (l'identifiant du badge)
  • A la fin un retour chariot

Le lecteur prend en charge les technologies haute et basse fréquence dans un seul dispositif, il s'intègre donc facilement dans les environnements multi-technologies et simplifie les migrations de technologies de cartes.

Organisation

Pour l'organisation du projet et la communication entre tous les membres du groupe, nous avons utilisé :

  • Subversion qui est un logiciel libre de gestion de versions hébergé sur le site RiouxSVN pour l'ensemble du code source du projet.
  • L'espace de stockage commun Google Drive pour tous les documents ressources.
  • Trello qui permet de savoir l'état de chaque objectif de chaque personne.
  • Gantt pour avoir une vue de la planification du projet.

Objectifs

Les utilisateurs peuvent s'authentifier par lecture d'un badge RFID ou par identifiants, et ont la possibilité de rechercher un article, de consulter le stock ainsi que commander l'ouverture/fermeture des casiers.

Répartition des tâches

Voir la répartition des tâches dans le projet.

Tâches à réaliser Priorité Itération
Authentification par badge haute 1
Authentification par identifiant basse 1
Rechercher un article haute 1
Consulter les stocks moyenne 2
Commander l'ouverture des casiers moyenne 3
Afficher l'état des casiers moyenne 3

Il est essentiel pour le client de pouvoir réaliser une Authentification par badge. L'Authentification par identifiant permet à un utilisateur de se connecter sans son badge. Comme c'est une fonctionalité de secours, elle a une priorité basse. La tâche sera tout de même réalisée dans l'itération 1 car elle est associée techniquement à l'Authentification par badge.

Gantt

gantt.png
Diagramme de Gantt

Outils de développement

Les outils logiciels :

Désignation Caractéristiques
Système de gestion de base de données MariaDB
Atelier de génie logiciel Bouml v7.9
Logiciel de gestion de version Subversion (RiouxSVN)
Générateurs de documentation Doxygen version 1.8
Environnement de développement Qt Creator et Qt Designer
API GUI Qt 5.11.2

La Raspberry Pi 3 :

Désignation Caractéristiques
Ordinateur Raspberry Pi 3
Écran Écran tactile 7'' 800x480
Système d'exploitation GNU/Linux Raspbian
raspberry-pi-3bplus.png
Raspberry Pi 3

Le lecteur RFID :

Désignation Caractéristiques
Modèle OMNIKEY 5427 CK
Clavier virtuelle "QWERTY"
Prise en charge Haute et basse fréquence
omnikeyr-5427-ck.png
OMNIKEY 5427 CK

Diagramme de cas d'utilisation

Dans cette partie, les cas d'utilisation sont :

  • S'authentifier : se connecter avec un badge RFID ou des identifiants
  • Rechercher un article : rechercher un article parmi la liste du matériel présent dans l'armoire
  • Consulter le stock : consulter la quantité, la disponibilité et le casier dans lequel se trouve un article
Prévoir d'insérer ce diagramme de cas d'utilisation Bouml à la place
diagramme-usecases.png
Diagramme de cas d'utilisation Bouml

Le protocole de communication

Le protocole e-stock définit l'ensemble des trames permettant de communiquer entre le SE (Système Embarqué EC) et la Raspberry Pi (IR) avec une liaison série. Il est orienté ASCII.

Le protocole e-stock est basée sur des trames requête/réponse.

Format des trames

Les trames sont composées d'un en-tête pour identifier leur type. L'entête est "`CASIERS`". Le délimiteur de fin de champ est le ';'. Le délimiteur de fin de trame "`\r\n`".

Trame de requête Raspberry Pi → SE :

CASIERS;requete;n_casier;\r\n

  • n_casier : 1 à 8 et 0 = broadcast
  • requete : 1 pour une commande d'ouverture de casier et 2 pour une demande d'état

Trame de réponse SE → Raspberry Pi :

CASIERS;reponse;n_casier;donnée;\r\n

  • n_casier : 1 à 8
Requête reponse donnée
pour une commande d'ouverture du casier 1 0 = erreur, 1 = ok
pour une demande d'état 2 0 = ouvert, 1 = fermé, 2 = inconnu

Remarque : dans le cas d'une requête en broadcast, les champs n_casier;donnée; sont répétés pour chaque casier.

msc_inline_mscgraph_1

Maquette IHM

La maquette de l'IHM était la suivante :

maquette.png

L'utilisateur doit quand il veut accéder au contenu de l'armoire s'authentifier soit par badge RFID soit par ses identifiants.

Pour s'authentifier avec un badge RFID (c'est le choix par défaut) : il doit présenter son badge

authentification-badge.png

Pour s'authentifier par ces identifiants : il doit saisir son identifiant et son mot de passe

authentification-identifiant.png

L'utilisateur une fois connecté pourra effectuer une recherche de l'article qu'il souhaite et consulter les stocks de l'article concerné :

consulter-stock.png

Scénarios

Scénario Sélection du type d'authentification

L'utilisateur a le choix pour s'authentifier, il peut utiliser :

  • son badge : comme la trame lue contient un retour à la ligne, cela provoquera l'émission du signal editingFinished() qui va déclencher l'authentification par badge ;
  • ses identifiants : l'utilisateur doit d'abord cliquer sur le bouton "Par identifiant", puis saisir son identifiant et mot de passe et enfin cliquer sur le bouton "Se connecter" qui provoquera l'émission du signal clicked() et déclenchera l'authentification par identifiant.

Dans de ce scénario, seule la classe Ihm intervient car c'est son rôle d'interagir avec l'utilisateur :

classe-ihm.png
La classe Ihm

Voir le diagramme de classes complet.

Voir :

Scénario Authentification par badge

Une fois le "badge" réceptionné par la classe Rfid, il faut corriger le contenu de la trame car le lecteur de badge est configuré en clavier virtuel "QWERTY". Il faut traduire les caractères en "AZERTY" avec la méthode Rfid::corrigerBadge().

Puis on vérifie que le début de la trame "badge" contient "RFID:" sinon on envoie une erreur avec Rfid::erreurBadgeInvalide() et on l'affiche avec Ihm::afficherErreurBadge(). Si la trame contient un entête valide, extrait l'UID et on le signale. Il faut ensuite le vérifier avec Supervision::verifierAuthentificationBadge().

Puis on récupère les données liées au badge dans la base de données avec Supervision::recupererDonneesUtilisateur() qui appelle la méthode Bdd::recuperer() pour exécuter la requête SQL. On vérifie la présence de données utilisateur sinon on envoie l'erreur utilisateur invalide avec Supervision::reponseDemandeDeConnexion(). Si les données sont présentes, on vérifie ensuite la date de validité du compte pour envoyer une réponse qui sera ensuite traitée par l'Ihm avec Ihm::traiterDemandeDeConnexion(). En fonction de la réponse, on appellera soit la méthode Ihm::allerFenetreMenu() soit on affichera une boîte de dialogue d'erreur. Pour finir, Supervision connectera l'utilisateur avec les données récupérées précédemment.

sd-authentification-badge.png
Scénario Authentification par badge

Voici le diagramme de classes pour ce scénario :

classes-scenario-authentification-badge.png
Diagramme de classes partiel pour l'authentification par badge

La méthode Ihm::authentifierParBadge() permet de signaler la trame d'un badge lu par le lecteur si celle-ci n'est pas vide :

ui->labelErreurBadge->clear();
if(ui->lineBadge->text() != "")
{
#ifdef DEBUG_IHM
qDebug() << Q_FUNC_INFO << "Contenu brut badge" << ui->lineBadge->text();
#endif
QString trameBadge = ui->lineBadge->text();
ui->lineBadge->clear();
emit badgeDetecte(trameBadge);
}
Voir également
https://doc.qt.io/qt-5/qlabel.html

La méthode Rfid::corrigerBadge() traduit les "caractères QWERTY" de la trame du badge vers l'"AZERTY" :

QString Rfid::corrigerBadge(QString badge)
{
QString badgeCorrige = "";
if(!badge.isEmpty())
{
// effectue les remplacements des touches QWERTY en touches AZERTY
badgeCorrige = badge.replace(QString::fromUtf8("Q"), "A");
badgeCorrige = badge.replace(QString::fromUtf8("W"), "Z");
badgeCorrige = badge.replace(QString::fromUtf8("q"), "q");
badgeCorrige = badge.replace(QString::fromUtf8("w"), "z");
badgeCorrige = badge.replace(QString::fromUtf8("M"), ":");
badgeCorrige = badge.replace(QString::fromUtf8("à"), "0");
badgeCorrige = badge.replace(QString::fromUtf8("&"), "1");
badgeCorrige = badge.replace(QString::fromUtf8("é"), "2");
badgeCorrige = badge.replace(QString::fromUtf8("\""), "3");
badgeCorrige = badge.replace(QString::fromUtf8("'"), "4");
badgeCorrige = badge.replace(QString::fromUtf8("("), "5");
badgeCorrige = badge.replace(QString::fromUtf8("-"), "6");
badgeCorrige = badge.replace(QString::fromUtf8("è"), "7");
badgeCorrige = badge.replace(QString::fromUtf8("_"), "8");
badgeCorrige = badge.replace(QString::fromUtf8("ç"), "9");
}
return badgeCorrige;
}
Voir également
https://doc.qt.io/qt-5/qstring.html

La méthode Supervision::verifierAuthentificationBadge() va vérifier les données utilisateur récupérés à partir d'une requête SQL et connecter l'utilisateur si besoin :

{
QString requeteBDD = "SELECT * from Utilisateur where Badge = '" + badge + "';";
QStringList donnees = recupererDonneesUtilisateur(requeteBDD);
{
}
}
Voir également
https://doc.qt.io/qt-5/qstringlist.html

La méthode Supervision::recupererDonneesUtilisateur() effectue une demande à la classe Bdd qui effectuera une requête SQL et retournera les données obtenues :

QStringList Supervision::recupererDonneesUtilisateur(QString requeteBDD)
{
QStringList donnees;
bdd->recuperer(requeteBDD, donnees);
return donnees;
}

La méthode Supervision::verifierDonneesUtilisateur() vérifie que les données ne sont pas vides puis ensuite que la date de validité est correcte :

bool Supervision::verifierDonneesUtilisateur(QStringList &donnees)
{
#ifdef DEBUG_SUPERVISON
qDebug() << Q_FUNC_INFO << donnees;
#endif
if(!donnees.isEmpty())
{
{
emit reponseDemandeDeConnexion(true, "");
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}

La méthode Supervision::connecterUtilisateur() vérifiera si un utilisateur n'est pas déjà connecté sinon créera un objet Utilisateur avec ces données :

void Supervision::connecterUtilisateur(QStringList &donnees)
{
if(utilisateur != nullptr)
{
}
utilisateur = new Utilisateur(donnees, this);
#ifdef DEBUG_SUPERVISION
qDebug() << Q_FUNC_INFO << utilisateur->getIdentifiantUtilisateur() << "authentifié";
#endif
}
Voir également
Utilisateur::Utilisateur(QStringList donnees, QObject *parent)

Pour l'authentification par badge, on utilise la requête SQL suivante :

SELECT * FROM Utilisateur WHERE Badge = '" + badge + "';

Cette requête permet de récupérer les informations de l'utilisateur en fonction de l'UID du badge.

table-utilisateur.png
La table Utilisateur

Scénario Authentification par identifiant

On récupère la saisie des identifiants de l'utilisateur et on le signale avec Ihm::identifiantDetecte() qui déclenche Supervision::verifierAuthentificationIdentifiant() en lui passant en paramètre l'identifiant et le mot de passe. Cette méthode va ensuite crypter le mot de passe avec Supervision::crypterMotDepasse(), puis récupérer les données de la base de données en fonction de l'identifiant et du mot de passe crypté avec la méthode Bdd::recuperer(). On fera appel à la méthode Supervision::verifierDonneesUtilisateur() qui vérifie la présence de données utilisateur sinon on envoie l'erreur utilisateur invalide avec Supervision::reponseDemandeDeConnexion(). Si les données sont présentes, on vérifie ensuite la date de validité du compte pour envoyer une réponse qui sera ensuite traitée par l'Ihm avec Ihm::traiterDemandeDeConnexion(). En fonction de la réponse, on appellera soit la méthode Ihm::allerFenetreMenu() soit on affichera une boîte de dialogue d'erreur. Pour finir, Supervision connectera l'utilisateur avec les données récupérées précédemment.

sd-authentification-identifiant.png
Scénario Authentification par identifiant

Voici le diagramme de classes pour ce scénario :

classe-sd-authentification-identifiant.png
Diagramme de classes partiel pour l'authentification par identifiant

La méthode authentifierParIdentifiant() qui permet d'envoyer les champs identifiant et mot de passe s'ils ne sont pas vides :

if(ui->lineIdentifiant->text() != "")
{
#ifdef DEBUG_IHM
qDebug() << Q_FUNC_INFO << "Identifiant" << ui->lineIdentifiant->text() << "MotDePasse" << ui->lineMotDePasse->text();
#endif
QString identifiant = ui->lineIdentifiant->text();
QString motDePasse = ui->lineMotDePasse->text();
ui->lineIdentifiant->clear();
ui->lineMotDePasse->clear();
emit identifiantDetecte(identifiant, motDePasse);
}
Voir également
https://doc.qt.io/qt-5/qlabel.html

La méthode verifierAuthentificationIdentifiant() demandera les informations utilisateur relatives à l'identifiant et au mot de passe crypté, si les données sont correctes on connectera l'utilisateur si nécessaire :

void Supervision::verifierAuthentificationIdentifiant(QString identifiant, QString motDePasse)
{
this->crypterMotDepasse(motDePasse);
#ifdef CHANGE_PASSWORD_BEFORE
QString requete = QString("UPDATE Utilisateur SET MotDePasse='%1' WHERE Identifiant='%2'").arg(motDePasse).arg(identifiant);
bdd->executer(requete);
#endif
QString requeteBDD = "SELECT * from Utilisateur where Identifiant = '" + identifiant + "' && MotDePasse = '" + motDePasse + "';";
QStringList donnees = recupererDonneesUtilisateur(requeteBDD);
{
}
}
Voir également
https://doc.qt.io/qt-5/qstringlist.html

La méthode privée crypterMotDepasse() effectue le cryptage du mot de passe seulement si celui-ci n'est pas vide :

void Supervision::crypterMotDepasse(QString &motDePasse)
{
if(!motDePasse.isEmpty())
{
motDePasse = QString(QCryptographicHash::hash((motDePasse).toLatin1(), QCryptographicHash::Md5).toHex());
}
#ifdef DEBUG_SUPERVISION
qDebug() << Q_FUNC_INFO << "Mot de passe crypte" << motDePasse;
#endif
}
Voir également
https://doc.qt.io/qt-5/qcryptographichash.html

Pour l'authentification par identifiant, on utilise la requête SQL suivante :

SELECT * FROM Utilisateur WHERE Identifiant = '" + identifiant + "' && MotDePasse = '" + motDePasse + "';

Cette requête permet de récupérer les informations de l'utilisateur en fonction de son identifiant et mot de passe.

table-utilisateur.png
La table Utilisateur

Scénario Recherche article

L'utilisateur saisi un nom de l'article et déclenche l'exécution de la méthode Ihm::rechercherArticle() en cliquant sur le bouton "Rechercher". On transmet le traitement à la classe Supervision en appelant Supervision::rechercherArticle() avec en paramètre le nom d'article à rechercher.

Supervision récupérera les articles qui corresponde avec la méthode Bdd::recuperer(). Puis elle renverra la liste des articles à l'Ihm avec la méthode Ihm::mettreAJourListeArticle(). Cette dernière a pour rôle de mettre à jour la liste déroulante contenant la liste d'articles trouvés suite à la recherche effectuée.

sd-rechercher-article.png
Scénario Recherche article

Voici le diagramme de classes pour ce scénario :

classe-sd-rechercher-article.png
Diagramme de classes partiel pour la recherche d'un article

La méthode rechercherArticle() de la classe Ihm qui récupère le contenu de la recherche et le signale à la classe Supervision :

{
if(!ui->lineRecherche->text().isEmpty())
supervision->rechercherArticle(ui->lineRecherche->text());
}

La méthode rechercherArticle() de la classe Supervision va demander à la classe Bdd de récupérer la liste des articles qui "ressemble" au contenu recherché :

void Supervision::rechercherArticle(QString recherche)
{
QString requete = "SELECT Stock.NumeroCasier, Article.idType, Article.Nom, Stock.Quantite, Stock.Disponible, Article.Designation FROM Stock INNER JOIN Article ON Stock.idArticle = Article.idArticle WHERE Article.Nom LIKE '%" + recherche + "%' OR Article.Code LIKE '%" + recherche + "%' OR Article.Designation LIKE '%" + recherche + "%' ORDER BY Stock.NumeroCasier ASC";
QVector<QStringList> listeArticlesTrouves;
bdd->recuperer(requete, listeArticlesTrouves);
emit articlesTrouves(listeArticlesTrouves);
}

La méthode mettreAJourListeArticles() met à jour la liste des articles trouvés dans la liste déroulante. Une déconnexion/connexion signal/slot est nécessaire pour éviter un déclenchement pendant la mise à jour :

#ifdef DEBUG_IHM
qDebug() << Q_FUNC_INFO << "articlesTrouves" << articlesTrouves.size() << articlesTrouves;
#endif
creerListeArticles(articlesTrouves);
effacerRechercheArticle();
Voir également
https://doc.qt.io/qt-5/qcombobox.html

Pour récupérer les informations de l'article en fonction du nom, on effectue une requête SQL qui utilise LIKE. Les informations sur l'article recherché nécessite d'effectuer une jointure entre les tables "Stock" et "Article" :

SELECT Stock.NumeroCasier, Article.idType, Article.Nom, Stock.Quantite, Stock.Disponible, Article.Designation FROM Stock
INNER JOIN Article ON Stock.idArticle = Article.idArticle
WHERE Article.Nom LIKE '%" + recherche + "%' OR Article.Code LIKE '%" + recherche + "%' OR Article.Designation LIKE '%" + recherche + "%'
ORDER BY Stock.NumeroCasier ASC
tables-stock-article.png
Les tables Stock et Article
Voir également
https://dev.mysql.com/doc/refman/8.0/en/pattern-matching.html
https://dev.mysql.com/doc/refman/8.0/en/join.html

Scénario Consulter le stock

L'utilisateur sélectionne un article dans la liste déroulante. Cela déclenche le slot Ihm::selectionnerArticle() qui appelle Supervision::selectionnerArticle() avec en paramètre le nom de l'article sélectionné. Un objet Article est alors instancié. Il faut récupérer le nombre de casiers de l'armoire car il est possible qu'un article soit réparti dans plusieurs casiers. On appelle alors la méthode static Article::recupererNombreCasiersPourNomArticle().

Pour chaque casier, on récupère les données de l'article avec Article::recupererDonneesArticleParNumeroCasier() puis, si des données ont été récupérées, on appelle Ihm::afficherDonneesArticleSelectionne() pour les afficher.

sd-consulter-stock.png
Scénario Consulter le stock

Voici le diagramme de classes pour ce scénario :

classes-sd-consulter-stock.png
Diagramme de classes partiel pour la consultation du stock
consulter-stock.png
Six articles Fluke i30s sont présents dans le casier 1

La méthode selectionnerArticle() de la classe Ihm envoie le nom de l'article sélectionné dans la liste déroulante à partir de son index :

#ifdef DEBUG_IHM
qDebug() << Q_FUNC_INFO << "index" << index << ui->comboBoxArticle->currentText();
#endif
supervision->selectionnerArticle(ui->comboBoxArticle->currentText());
Voir également
https://doc.qt.io/qt-5/qcombobox.html

La méthode selectionnerArticle() de la classe Supervision récupère les données de l'article différemment si l'article est dans un casier ou plusieurs, puis les envoie à l'ihm pour les afficher :

void Supervision::selectionnerArticle(QString nomArticle)
{
#ifdef DEBUG_SUPERVISION
qDebug() << Q_FUNC_INFO << "Nom article" << nomArticle;
#endif
Article *article = new Article(this);
QVector<QStringList> donneesArticle;
QStringList donnees;
unsigned int nombreCasiers = Article::recupererNombreCasiersPourNomArticle(nomArticle);
#ifdef DEBUG_SUPERVISION
qDebug() << Q_FUNC_INFO << "nombreCasiers" << nombreCasiers;
#endif
if(nombreCasiers > 1)
{
QVector<QString> numeroDesCasiers;
numeroDesCasiers = Article::recupererNumeroCasierPourNomArticle(nomArticle);
for(int i = 0; i < numeroDesCasiers.size(); i++)
{
article->recupererDonneesArticleParNumeroCasier(numeroDesCasiers[i]);
ajouterDonneesArticle(article, donneesArticle, donnees);
}
if(!donneesArticle.isEmpty())
{
emit donneesArticleSelectionne(donneesArticle);
}
}
else
{
article->recupererDonneesArticleParNom(nomArticle);
ajouterDonneesArticle(article, donneesArticle, donnees);
if(!donneesArticle.isEmpty())
{
emit donneesArticleSelectionne(donneesArticle.at(0));
}
}
}
Voir également
https://doc.qt.io/qt-5/qvector.html

La méthode afficherDonneesArticleSelectionne() pour un article affiche simplement les données dans l'interface graphique :

if(donneesArticle.size() <= 0)
return;
unsigned int articleQuantite = 0;
unsigned int articleDisponible = 0;
QString casiersQuantite;
QString casiersDisponible;
QString casiers;
int nombreCasiers = donneesArticle.size();
for(int i = 0; i < nombreCasiers; i++)
{
#ifdef DEBUG_IHM
qDebug() << Q_FUNC_INFO << "disponible" << (donneesArticle[i].at(ARTICLE_DISPONIBLE)).toUInt();
qDebug() << Q_FUNC_INFO << "articleDisponible" << articleDisponible;
qDebug() << Q_FUNC_INFO << "quantite" << (donneesArticle[i].at(ARTICLE_QUANTITE)).toUInt();
qDebug() << Q_FUNC_INFO << "articleQuantite" << articleQuantite;
#endif
articleDisponible += (donneesArticle[i].at(ARTICLE_DISPONIBLE)).toUInt();
articleQuantite += (donneesArticle[i].at(ARTICLE_QUANTITE)).toUInt();
if(i == 0)
{
casiers = donneesArticle[i].at(NUMERO_CASIERS);
casiersDisponible = QString(" (") + donneesArticle[i].at(ARTICLE_DISPONIBLE);
casiersQuantite = QString(" (") + donneesArticle[i].at(ARTICLE_QUANTITE);
}
else
{
casiers += " et " + donneesArticle[i].at(NUMERO_CASIERS);
casiersDisponible += QString(" et ") + donneesArticle[i].at(ARTICLE_DISPONIBLE);
casiersQuantite += QString(" et ") + donneesArticle[i].at(ARTICLE_QUANTITE);
}
}
casiersDisponible += QString(")");
casiersQuantite += QString(")");
ui->labelCasier->setText("Casiers :");
ui->labelQuantiteNombre->setText(QString::number(articleQuantite) + casiersQuantite);
ui->labelDisponibleNombre->setText(QString::number(articleDisponible) + casiersDisponible);
ui->labelCasierNombre->setText(casiers);

La méthode afficherDonneesArticleSelectionne() pour un article dans plusieurs casiers traite l'ensemble des données reçues pour déterminer la quantité et disponibilités totales afin de les afficher :

if(donneesArticle.size() <= 0)
return;
unsigned int articleQuantite = 0;
unsigned int articleDisponible = 0;
QString casiersQuantite;
QString casiersDisponible;
QString casiers;
int nombreCasiers = donneesArticle.size();
for(int i = 0; i < nombreCasiers; i++)
{
#ifdef DEBUG_IHM
qDebug() << Q_FUNC_INFO << "disponible" << (donneesArticle[i].at(ARTICLE_DISPONIBLE)).toUInt();
qDebug() << Q_FUNC_INFO << "articleDisponible" << articleDisponible;
qDebug() << Q_FUNC_INFO << "quantite" << (donneesArticle[i].at(ARTICLE_QUANTITE)).toUInt();
qDebug() << Q_FUNC_INFO << "articleQuantite" << articleQuantite;
#endif
articleDisponible += (donneesArticle[i].at(ARTICLE_DISPONIBLE)).toUInt();
articleQuantite += (donneesArticle[i].at(ARTICLE_QUANTITE)).toUInt();
if(i == 0)
{
casiers = donneesArticle[i].at(NUMERO_CASIERS);
casiersDisponible = QString(" (") + donneesArticle[i].at(ARTICLE_DISPONIBLE);
casiersQuantite = QString(" (") + donneesArticle[i].at(ARTICLE_QUANTITE);
}
else
{
casiers += " et " + donneesArticle[i].at(NUMERO_CASIERS);
casiersDisponible += QString(" et ") + donneesArticle[i].at(ARTICLE_DISPONIBLE);
casiersQuantite += QString(" et ") + donneesArticle[i].at(ARTICLE_QUANTITE);
}
}
casiersDisponible += QString(")");
casiersQuantite += QString(")");
ui->labelCasier->setText("Casiers :");
ui->labelQuantiteNombre->setText(QString::number(articleQuantite) + casiersQuantite);
ui->labelDisponibleNombre->setText(QString::number(articleDisponible) + casiersDisponible);
ui->labelCasierNombre->setText(casiers);

Dans ce scénario, on utilise plusieurs requêtes SQL :

  • Pour récupérer les informations d'un article en fonction de son nom :
SELECT Stock.idStock, Article.idArticle, Article.Nom AS Article, Type.idType, Type.nom AS Type, Comptage.idComptage, Comptage.Nom AS Comptage, Article.Code, Article.Designation, Stock.Quantite, Stock.Disponible, Article.Poids, Stock.Tare, Unite.idUnite, Unite.Nom, Stock.numeroCasier FROM Stock
INNER JOIN Article ON Article.idArticle=Stock.idArticle
INNER JOIN Type ON Type.idType=Article.idType
INNER JOIN Comptage ON Comptage.idComptage=Stock.idComptage
INNER JOIN Unite ON Unite.idUnite=Stock.idUnite
WHERE Article.Nom = '" + nomArticle + "';"
  • Pour récupérer les informations d'un article en fonction de son numéro de casier :
SELECT Stock.idStock, Article.idArticle, Article.Nom AS Article, Type.idType, Type.nom AS Type, Comptage.idComptage, Comptage.Nom AS Comptage, Article.Code, Article.Designation, Stock.Quantite, Stock.Disponible, Article.Poids, Stock.Tare, Unite.idUnite, Unite.Nom, Stock.numeroCasier FROM Stock
INNER JOIN Article ON Article.idArticle=Stock.idArticle
INNER JOIN Type ON Type.idType=Article.idType
INNER JOIN Comptage ON Comptage.idComptage=Stock.idComptage
INNER JOIN Unite ON Unite.idUnite=Stock.idUnite
WHERE Stock.numeroCasier = '" + numeroCasier + "';"
  • Pour récupérer le nombre de casier pour un article en fonction du nom :
SELECT COUNT(Stock.idArticle) FROM Stock
INNER JOIN Article ON Stock.idArticle = Article.idArticle
WHERE Article.Nom = '" + nomArticle + "';"
  • Pour récupérer les numéros de casiers associer au nom d'un article :
SELECT Stock.numeroCasier FROM Stock
INNER JOIN Article ON Article.idArticle=Stock.idArticle
INNER JOIN Type ON Type.idType=Article.idType
INNER JOIN Comptage ON Comptage.idComptage=Stock.idComptage
INNER JOIN Unite ON Unite.idUnite=Stock.idUnite
WHERE Article.Nom = '" + nomArticle + "';
schema-bdd.png
La base de données e-stock

Tests de validation

  • Rechercher un article
Test Article Résultats attendus Résultats obtenus Validation
Recherche mot approché (casse) fluke Fluke i30s
Fluke 179
Fluke i30s
Fluke 179
Oui
Recherche mot exact Fluke i30s Fluke i30s Fluke i30s Oui
Recherche d'un article non existant test Aucun article Aucun article Oui
  • Consulter le stock
Test Article Résultats attendus Résultats obtenus Validation
Consultation un article dans un casier Fluke 179 Quantité : 2
Disponible : 2
Casier : 2
Quantité : 2
Disponible : 2
Casier : 2
Oui
Consultation un article dans plusieurs casiers Fluke i30s Quantité : 8
Disponible : 6
Casier : 1 et 3
Quantité : 8
Disponible : 6
Casier : 1 et 3
Oui
  • Authentification par identifiant
Test Identifiant/Mot de passe Résultats attendus Résultats obtenus Validation
Identifiant et mot de passe valide jbeaumont/"" Affichage fenêtre stock Affichage fenêtre stock Oui
Identifiant et mot de passe valide et date invalide bounoir.f/"" Affichage "Erreur : le compte n'est plus valide" Affichage "Erreur : le compte n'est plus valide" Oui
Identifiant valide et mot de passe invalide jbeaumont/test Affichage "Erreur : utilisateur non valide" Affichage "Erreur : utilisateur non valide" Oui
Identifiant valide et mot de passe invalide test/"" Affichage "Erreur : utilisateur non valide" Affichage "Erreur : utilisateur non valide" Oui
  • Authentification par badge RFID
Test Badge Résultats attendus Résultats obtenus Validation
Badge non valide RGFD:30DDA983 Affichage "Erreur badge invalide" Affichage "Erreur badge invalide" Oui
Badge valide 30DDA983 Affichage fenêtre stock Affichage fenêtre stock Oui
Badge valide mais date de validité invalide 62A3F560 Affichage "Erreur : le compte n'est plus valide" Affichage "Erreur : le compte n'est plus valide" Oui
Badge non présent dans la base de données 335C3086 Affichage "Erreur : utilisateur non valide" Affichage "Erreur : utilisateur non valide" Oui

Recette

Fonctionnalités réalisées :

  • S'authentifier
  • Rechercher un article
  • Consulter le stock

Fonctionnalités en cours :

  • Communiquer avec le SE pour :
    • Commander l'ouverture/fermeture des casiers
    • Afficher l'état ouvert/fermé des casiers

Informations

Auteur
Pierre-Antoine Legger <pierr.nosp@m.eant.nosp@m.oinel.nosp@m.egge.nosp@m.r@gma.nosp@m.il.c.nosp@m.om>
Date
2020
Version
0.2
Voir également
https://svn.riouxsvn.com/e-stock