Conception et développement d'une application desktop complète de gestion d'affaires et de devis . Cette solution métier sur mesure intègre deux interfaces distinctes (Espace Client et Espace Vendeur) partageant une base de données SQLite centralisée, avec génération automatique de devis PDF professionnels, système de commentaires intégré, négociation d'options et workflow de suivi d'affaires complet.
Contexte et problématique métier
L'entreprise conçoit et fabrique des hexapodes de haute précision — systèmes de positionnement à 6 degrés de liberté — pour des secteurs exigeants : aérospatial (tests satellites, simulateurs de vol), recherche scientifique (synchrotrons, observatoires), et industrie de pointe (métrologie, assemblage de précision). Au-delà des hexapodes, le catalogue couvre également des bras robotiques, tables vibrantes, bancs d'essai hydrauliques, plateformes de simulation et systèmes de positionnement. Chaque affaire client est unique et nécessite :
- Configuration personnalisée — Choix du modèle d'équipement adapté à la charge et à la précision requises, sélection d'options standards (universelles ou spécifiques au produit) et options sur-mesure avec contrôle du poids (85 % de la charge max), compatibilités environnementales spéciales (ultra-vide, salle blanche, IP65, anticorrosion marine)
- Négociation bidirectionnelle — Cycle de devis versionnés (V1, V2…) avec statuts par option (en attente, accepté, refusé, contre-proposition), permettant au vendeur et au client de négocier chaque ligne individuellement
- Communication centralisée — Échanges documentés et horodatés entre le client et le vendeur via un système de commentaires intégré, avec distinction visuelle par rôle (vert = acheteur, rouge = vendeur)
- Devis itératifs — Gestion automatique des versions avec historique complet, conclusion automatique lorsque toutes les options sont acceptées, et génération de devis PDF professionnels conformes à la charte de l'entreprise
Problèmes identifiés avant le projet :
- Processus de devis manuel sous Word/Excel, chronophage et source d'erreurs de calcul
- Difficultés de suivi de l'avancement des affaires — informations dispersées
- Communication éparpillée entre emails, téléphone et réunions sans historique centralisé
- Absence de traçabilité des échanges, décisions et modifications de devis
- Génération de documents non standardisée impactant l'image professionnelle
- Pas de visibilité temps réel pour les clients sur l'état de leur affaire
Objectifs et solutions apportées :
- Créer un espace client sécurisé permettant de soumettre des demandes de devis, configurer les produits avec options, suivre l'avancement et négocier directement avec le vendeur
- Développer une interface vendeur complète pour gérer l'ensemble des affaires, répondre aux demandes clients (accepter, refuser ou contre-proposer chaque option), clôturer les affaires et générer les devis
- Automatiser la génération de devis PDF professionnels avec calculs automatiques (HT, TVA 20 %, TTC), conditions de paiement et mentions légales
- Centraliser tous les échanges et commentaires dans une base de données unique avec historique consultable et rafraîchissement automatique
- Implémenter un système de négociation par option avec 4 statuts, versioning automatique des devis et conclusion automatique d'affaire
Architecture technique globale
L'application repose sur une architecture modulaire Python suivant le pattern MVC (Modèle-Vue-Contrôleur) avec séparation stricte des responsabilités. Le modèle utilise le pattern Repository pour l'accès aux données et le pattern Facade (DatabaseManager) comme point d'entrée unique. Les contrôleurs (BaseController → Client/VendeurController) encapsulent la logique métier, et les vues ne communiquent jamais directement avec la base de données :
Structure du projet
devis/
├── main.py # Launcher principal avec choix Client/Vendeur/Les deux
├── client_app.py # Application Espace Client (interface PyQt6, thème bleu)
├── vendeur_app.py # Application Espace Vendeur (interface PyQt6, thème rouge)
├── requirements.txt # Dépendances Python : PyQt6, fpdf2
├── assets/
│ └── logo.jpg # Logo entreprise pour les en-têtes PDF
├── data/
│ ├── devis_database.db # Base SQLite centralisée (11 tables)
│ ├── session_client.json # Session persistante client
│ └── session_vendeur.json # Session persistante vendeur
├── generated_devis/ # Répertoire des PDFs générés
├── tests/ # 71 tests unitaires et d'intégration (pytest)
│ ├── conftest.py # Fixture BDD temporaire pour les tests
│ ├── test_auth.py # Tests hachage, vérification, force mot de passe
│ ├── test_siret.py # Tests format, algorithme de Luhn
│ ├── test_constants.py # Tests constantes métier et helpers statuts
│ ├── test_repositories.py # Tests CRUD Auth, Affaire, Devis, Produit
│ └── test_controllers.py # Tests VendeurController, ClientController
└── src/
├── constants/ # Constantes métier, statuts, couleurs PDF
│ ├── business.py # TVA, limite poids, validité, infos entreprise, CGV
│ ├── statuts.py # Couleurs et emojis des statuts, rôles
│ └── pdf.py # Palette RGB pour les PDF (bleu, gris, rouge…)
├── controllers/ # Logique applicative (pattern Template Method)
│ ├── base_controller.py # Méthodes communes (affaires, devis, PDF, commentaires)
│ ├── client_controller.py # Inscription, création d'affaire, soumission devis
│ └── vendeur_controller.py # Réponses vendeur, clôture d'affaire
├── models/ # Couche d'accès aux données
│ ├── database.py # Connexion SQLite, schéma 11 tables, migrations, démo
│ ├── db_manager.py # Facade DatabaseManager (point d'entrée unique)
│ └── repositories/ # Pattern Repository (accès données spécialisés)
│ ├── auth_repo.py # Authentification client et vendeur
│ ├── affaire_repo.py # CRUD affaires, clients, commentaires
│ ├── devis_repo.py # Devis, versioning, options, négociation
│ └── produit_repo.py # Catalogue produits et options
├── utils/ # Fonctions utilitaires transversales
│ ├── auth.py # PBKDF2-SHA256, force mdp, indicatifs téléphoniques
│ ├── pdf_generator.py # Génération PDF professionnels (fpdf)
│ ├── session.py # Persistance session JSON par rôle
│ └── siret_validator.py # Validation SIRET (format + Luhn + API gouv.fr)
└── views/ # Interface utilisateur PyQt6
├── theme.py # Palettes sombres, styles QSS, classe S
├── base_window.py # Fenêtre abstraite partagée (Template Method)
├── auth_dialogs.py # Inscription/connexion, TelephoneWidget, PasswordStrength
├── devis_form.py # Formulaire de création de devis
├── dialogs.py # Dialogues détail devis et réponse options
├── widgets.py # ProduitConfigWidget (modèle, options, barre poids)
├── client_dialogs.py # Dialogues spécifiques client
└── vendeur_dialogs.py # Dialogues spécifiques vendeurDiagramme d'architecture
┌─────────────────────────────────────────────────────────────────────────┐ │ LAUNCHER (main.py) │ │ ┌─────────────┬─────────────┐ │ │ │ CLIENT │ VENDEUR │ │ │ │ APP │ APP │ │ │ │ (bleu) │ (rouge) │ │ │ └──────┬──────┴──────┬──────┘ │ ├───────────────────────────┼─────────────┼───────────────────────────────┤ │ CONTROLLERS (Logique métier — MVC) │ │ ┌───────────────────────▼─────────────▼───────────────────────┐ │ │ │ BASE CONTROLLER │ │ │ │ (affaires, devis, PDF, commentaires) │ │ │ │ │ │ │ │ ┌────────────────────┐ ┌────────────────────┐ │ │ │ │ │ ClientController │ │ VendeurController │ │ │ │ │ │ • inscription │ │ • authentification │ │ │ │ │ │ • création affaire │ │ • réponses options │ │ │ │ │ │ • soumission devis │ │ • contre-proposit. │ │ │ │ │ │ • validation poids │ │ • clôture affaire │ │ │ │ │ └────────────────────┘ └────────────────────┘ │ │ │ └─────────────────────────┬───────────────────────────────────┘ │ │ │ │ │ DATABASE MANAGER (Facade Pattern) │ │ ┌─────────────────────────▼───────────────────────────────────┐ │ │ │ db_manager.py — Point d'entrée unique │ │ │ │ │ │ │ │ ┌────────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ AuthRepo │ │AffaireRepo │ │DevisRepo │ │ProduitR. │ │ │ │ │ │ • vendeur │ │ • affaires │ │ • devis │ │ • produit│ │ │ │ │ │ • client │ │ • clients │ │ • version│ │ • option │ │ │ │ │ │ • auth │ │ • comment. │ │ • options│ │ • liaison│ │ │ │ │ └────────────┘ └────────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────┬───────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────▼───────────────────────────────────┐ │ │ │ SQLite Database │ │ │ │ (devis_database.db — 11 tables) │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ client │ │ vendeur │ │ affaire │ │ devis │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ ┌──────────┐ │ │ │ │ │ produit │ │ option │ │produit_option │ │commentai.│ │ │ │ │ └──────────┘ └──────────┘ └───────────────┘ └──────────┘ │ │ │ │ ┌──────────────┐ ┌─────────────────────────┐ ┌──────────┐ │ │ │ │ │produit_devis │ │ligne_option_produit_devis│ │opt_perso │ │ │ │ │ └──────────────┘ └─────────────────────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ PDF GENERATOR │ │ │ │ (pdf_generator.py — fpdf) │ │ │ │ │ │ │ │ • En-tête personnalisé avec logo entreprise │ │ │ │ • Tableaux produits avec options et statuts │ │ │ │ • Calculs automatiques HT/TVA 20%/TTC │ │ │ │ • Conditions générales et mentions légales │ │ │ │ • Pied de page avec capital social et RCS │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
Modèle de données — Schéma SQLite (11 tables)
Tables principales
| Table | Description | Colonnes principales |
|---|---|---|
client | Entreprises clientes avec coordonnées, SIRET et authentification | id, nom_societe, siret, contact_nom, contact_prenom, contact_service, contact_email, contact_telephone, indicatif_tel, username, password_hash |
vendeur | Comptes vendeurs avec authentification sécurisée | id, username, password_hash, nom, prenom, email, date_creation |
affaire | Affaires commerciales avec numérotation automatique (format AANNNN) | id, numero_affaire, client_id, titre, description, statut, date_creation, date_modification |
devis | Devis versionnés (V1, V2…) rattachés à une affaire | id, affaire_id, version, date_creation, total_estime, statut, notes |
produit | Catalogue des 5 équipements industriels avec charge max et prix de base | id, nom, charge_max, prix_base |
option | Options et accessoires (4 universelles + 20 spécifiques liées par produit) | id, nom, prix, poids, universelle |
produit_option | Liaison many-to-many entre produits et options spécifiques | id, produit_id, option_id (UNIQUE) |
produit_devis | Produits inclus dans un devis avec quantité et prix | id, devis_id, produit_id, quantite, prix_unitaire |
ligne_option_produit_devis | Options standard d'un produit dans un devis, avec double statut vendeur/acheteur | id, produit_devis_id, option_id, statut_vendeur, prix_propose, commentaire_vendeur, statut_acheteur, commentaire_acheteur |
option_personnalisee | Demandes spéciales (options sur-mesure) avec négociation prix/poids | id, produit_devis_id, description, prix_demande, poids, statut_vendeur, prix_propose, commentaire_vendeur, statut_acheteur, commentaire_acheteur |
commentaire | Fil de discussion entre client et vendeur, rattaché à une affaire | id, affaire_id, auteur, role, contenu, date_creation |
Relations et contraintes
client (1) ──────< (N) affaire affaire (1) ──────< (N) devis affaire (1) ──────< (N) commentaire devis (1) ──────< (N) produit_devis produit_devis (1) ─< (N) ligne_option_produit_devis produit_devis (1) ─< (N) option_personnalisee produit (N) ─────< (N) option (via produit_option)
Interface Client — Fonctionnalités détaillées
Authentification sécurisée
- Inscription — Formulaire complet avec validation : raison sociale, SIRET (vérifié format + algorithme de Luhn + API gouvernementale), nom, prénom, service, email, téléphone avec indicatif international (26 pays), et mot de passe robuste
- Connexion — Identifiant + mot de passe avec hachage PBKDF2-HMAC-SHA256 (100 000 itérations, sel aléatoire 32 octets), comparaison en temps constant (hmac.compare_digest) pour prévenir les attaques par timing
- Validation mot de passe — Indicateur de force en temps réel (PasswordStrengthLabel) : minimum 10 caractères, majuscule, minuscule, chiffre et caractère spécial obligatoires
- Session persistante — Fichier JSON local (session_client.json) pour reconnexion automatique entre les sessions
Gestion des affaires et devis
- Création d'affaire — Ouverture d'une nouvelle affaire avec titre et description, numérotation automatique (format AANNNN : année + incrément)
- Configuration produit — Widget ProduitConfigWidget avec sélection du modèle d'équipement (5 modèles au catalogue), quantité, options catalogue (cases à cocher), options personnalisées (demandes spéciales avec prix souhaité) et barre de progression du poids avec contrôle de la charge maximale (85 % du max autorisé)
- Suivi des négociations — Visualisation des réponses du vendeur par option (accepté, refusé, contre-proposition avec prix), possibilité d'accepter les contre-propositions ou de demander une nouvelle version
- Conclusion automatique — Lorsque toutes les options sont acceptées par les deux parties, une version FINAL du devis est automatiquement générée et l'affaire est conclue
- Commentaires — Fil de discussion avec le vendeur, messages horodatés avec distinction visuelle par rôle (vert = acheteur, rouge = vendeur)
- Rafraîchissement automatique — Mise à jour des données toutes les 10 secondes pour refléter les réponses du vendeur en temps réel
Interface Vendeur — Fonctionnalités complètes
Authentification vendeur
- Inscription — Création de compte avec nom, prénom, email au domaine de l'entreprise obligatoire, et mot de passe robuste (mêmes règles que le client)
- Connexion sécurisée — Même système PBKDF2 que le client, session persistante indépendante (session_vendeur.json)
Gestion centralisée des affaires
- Vue d'ensemble — Table de toutes les affaires avec numéro, client, titre, statut et nombre de devis, avec rafraîchissement automatique
- Détail affaire — Liste des versions de devis (V1, V2…) avec date et total estimé, actions contextuelles selon le statut
- Commentaires — Même fil de discussion que côté client, avec distinction visuelle des rôles
Réponse aux devis et négociation
- Réponse par option — Pour chaque option (standard ou personnalisée) du devis client, le vendeur peut : accepter, refuser, ou faire une contre-proposition avec prix proposé et commentaire
- Options personnalisées — Le vendeur peut proposer un prix et un poids pour les demandes spéciales du client
- Versioning automatique — Chaque réponse vendeur crée automatiquement une nouvelle version du devis (V2, V3…)
Clôture d'affaire
- Trois issues possibles — Gagnée (génère un devis FINAL), Perdue ou Annulée, avec commentaire de clôture
- Devis FINAL — Si l'affaire est gagnée, une version FINAL du devis est automatiquement créée à partir de la dernière version
- Export PDF — Génération de devis PDF professionnels à tout moment pour n'importe quelle version
Génération PDF — Détail technique
Classe DevisPDF (fpdf)
class DevisPDF(FPDF):
"""PDF personnalisé avec en-tête et pied de page professionnels."""
# Couleurs entreprise (centralisées dans src.constants.pdf)
BLEU_FONCE = PDF_COLORS['bleu_fonce'] # (41, 128, 185)
BLEU_CLAIR = PDF_COLORS['bleu_clair'] # (52, 152, 219)
GRIS_FONCE = PDF_COLORS['gris_fonce'] # (44, 62, 80)
GRIS_CLAIR = PDF_COLORS['gris_clair'] # (236, 240, 241)
def header(self):
# Logo + infos entreprise (depuis constants.business)
self.image("assets/logo.jpg", 10, 8, 40)
self.set_xy(120, 10)
self.multi_cell(80, 4,
f"{ENTREPRISE['nom']}\n"
f"{ENTREPRISE['adresse']}\n"
f"Tel: {ENTREPRISE['telephone']}\n"
f"Email: {ENTREPRISE['email']}\n"
f"SIRET: {ENTREPRISE['siret']}", align="R")
self.line(10, 45, 200, 45)
def footer(self):
# Capital social + RCS + numéro de page
self.set_y(-20)
self.cell(0, 4, f"Page {self.page_no()}/{{nb}} | Devis N-VX", align="C")
def tableau_produit(self, prod_nom, quantite, prix, options_std, options_perso):
# En-tête : Description | Qté | P.U. HT | Statut | Total HT
# Exclut automatiquement les options refusées
# Affiche le statut (emoji) de chaque option
...Structure du PDF généré
┌─────────────────────────────────────────────────────────────────┐ │ [LOGO] Entreprise SAS │ │ Zone Industrielle │ │ contact@entreprise.fr │ │ SIRET: XXX XXX XXX XXXXX │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ██████████████ DEVIS ██████ N° 26001-V2 ██████████████████ │ │ │ │ ┌─ CLIENT ──────────────┐ ┌─ DATE & VALIDITÉ ──────────────┐ │ │ │ ACME Corporation │ │ Date: 16/01/2026 │ │ │ │ │ │ Valide jusqu'au: 15/02/2026 │ │ │ └───────────────────────┘ └────────────────────────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ JORAN - Hexapode haute précision charges lourdes │ ├──────────────────────────┬──────┬──────────┬────────┬──────────┤ │ Description │ Qté │ P.U. HT │ Statut │ Total HT │ ├──────────────────────────┼──────┼──────────┼────────┼──────────┤ │ JORAN (base) │ 1 │ 145 000 €│ Inclus │ 145 000 €│ │ Résolution 50 nm │ 1 │ 22 000 €│ OK │ 22 000 €│ │ Plateau renforcé │ 1 │ 8 500 €│ CP │ 9 200 €│ │ [Perso] Fixation custom │ 1 │ 3 000 €│ OK │ 3 500 €│ ├──────────────────────────┴──────┴──────────┴────────┴──────────┤ │ Sous-total HT: 179 700 € │ │ TVA 20%: 35 940 € │ │ TOTAL TTC: 215 640 € │ ├─────────────────────────────────────────────────────────────────┤ │ CONDITIONS GÉNÉRALES DE VENTE │ │ - Validité du devis: 30 jours │ │ - Délai de livraison: 8-12 semaines │ │ - Conditions de paiement: 30% à la commande, 70% à livraison │ │ - Garantie: 2 ans pièces et main d'oeuvre │ ├─────────────────────────────────────────────────────────────────┤ │ Entreprise SAS - Capital XXX XXX EUR - RCS Paris Page 1/1 │ └─────────────────────────────────────────────────────────────────┘
Patterns de conception utilisés
1. Pattern Repository + Facade — Accès aux données
# Facade : point d'entrée unique pour tout accès aux données
class DatabaseManager:
"""Délègue chaque appel au repository concerné."""
def __init__(self, db_name="devis_database.db"):
self._db = Database(db_name)
self._produit = ProduitRepository(self._db.get_connection)
self._affaire = AffaireRepository(self._db.get_connection)
self._devis = DevisRepository(self._db.get_connection)
self._auth = AuthRepository(self._db.get_connection)
# API unifiée — les contrôleurs n'appellent que ces méthodes
def creer_affaire(self, client_id, titre, description=""):
return self._affaire.creer_affaire(client_id, titre, description)
def get_devis_affaire(self, affaire_id):
return self._devis.get_devis_affaire(affaire_id)
def authentifier_vendeur(self, username, password):
return self._auth.authentifier_vendeur(username, password)
# ...2. Pattern Template Method — Contrôleurs et fenêtres
# BaseController : méthodes communes, étendues par Client/VendeurController
class BaseController:
"""Couche intermédiaire MVC — les vues n'accèdent JAMAIS au DatabaseManager."""
def __init__(self, db: DatabaseManager):
self.db = db
def get_affaires(self):
return self.db.get_liste_affaires()
def generer_pdf(self, devis_id, client_nom):
return generer_devis_pdf(self.db, devis_id, client_nom=client_nom)
class ClientController(BaseController):
"""Logique métier client : validation poids, soumission, réponses."""
def valider_et_soumettre(self, affaire_id, produit_widgets, produits_data, commentaire):
# Vérifie que chaque produit respecte 85% de charge_max
for w in produit_widgets:
if not w.is_poids_valide():
return {'error': "Dépassement de charge..."}
devis_id, version = self.db.creer_devis_pour_affaire(affaire_id, produits_data)
return {'error': None, 'devis_id': devis_id, 'version': version}
class VendeurController(BaseController):
"""Logique métier vendeur : réponses, contre-propositions, clôture."""
def cloturer_affaire(self, affaire_id, resultat, commentaire=""):
if resultat == 'gagne':
# Crée automatiquement un devis FINAL
final_id, final_version = self.db.creer_nouvelle_version_devis(...)
return {'success': True, 'final_id': final_id}3. Pattern Session — Persistance par rôle
def sauvegarder_session(role: str, data: dict):
"""Sauvegarde la session sur disque (par rôle : client ou vendeur)."""
path = os.path.join("data", f"session_{role}.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def charger_session(role: str) -> dict | None:
"""Charge la session existante. Retourne None si absente."""
path = os.path.join("data", f"session_{role}.json")
if not os.path.exists(path):
return None
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def supprimer_session(role: str):
"""Supprime le fichier de session (déconnexion)."""
os.remove(os.path.join("data", f"session_{role}.json"))4. Pattern Observer — Signaux/slots PyQt6
# Widget ProduitConfigWidget — signaux pour recalcul automatique
class ProduitConfigWidget(QFrame):
"""Widget de configuration produit avec barre de poids."""
def __init__(self, controller, index, on_change_callback, on_remove_callback):
super().__init__()
self.combo_modele = QComboBox()
self.combo_modele.currentIndexChanged.connect(self.on_modele_change)
self.spin_quantite = QSpinBox()
self.spin_quantite.valueChanged.connect(self.on_change)
self.progress_poids = QProgressBar() # Barre de charge
# Checkboxes options → signal stateChanged → recalcul poids
# BaseAffaireWindow : rafraîchissement automatique via QTimer
class BaseAffaireWindow(QMainWindow):
def _start_auto_refresh(self):
self.timer = QTimer()
self.timer.timeout.connect(self._on_auto_refresh)
self.timer.start(10000) # Toutes les 10 secondesSécurité — Implémentation détaillée
Hachage de mots de passe (PBKDF2)
_ITERATIONS = 100_000
_SALT_LENGTH = 32 # octets
def hash_password(password: str) -> str:
"""Hache avec sel aléatoire (PBKDF2-HMAC-SHA256).
Stockage: 'sel_hex:hash_hex'
"""
salt = os.urandom(_SALT_LENGTH)
hash_bytes = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, _ITERATIONS)
return salt.hex() + ":" + hash_bytes.hex()
def verify_password(password: str, stored_hash: str) -> bool:
"""Comparaison en temps constant (anti timing attack)."""
salt_hex, hash_hex = stored_hash.split(":")
salt = bytes.fromhex(salt_hex)
hash_bytes = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, _ITERATIONS)
return hmac.compare_digest(hash_bytes.hex(), hash_hex)Validation SIRET (3 niveaux)
def valider_siret_complet(siret_brut: str) -> dict:
"""Validation complète en 3 étapes."""
# 1. Format — 14 chiffres uniquement
format_ok, result = valider_format_siret(siret_brut)
# 2. Algorithme de Luhn — vérification mathématique
if not valider_siret_luhn(siret):
return {"valide": False, "erreur": "Vérification algorithmique échouée"}
# 3. API gouvernementale — recherche-entreprises.api.gouv.fr
en_ligne, nom = verifier_siret_en_ligne(siret) # timeout 5s
return {"valide": True, "siret": siret, "nom_entreprise": nom}Protection SQL
- Requêtes paramétrées — Toutes les requêtes SQL utilisent des paramètres liés (
?), aucune concaténation de chaînes. Prévention complète des injections SQL.
Tests — Suite complète (71 tests)
Organisation des tests
| Fichier | Nb tests | Couverture |
|---|---|---|
test_auth.py | 11 | Format hash (sel:hash), unicité des sels, vérification correcte, règles de force mot de passe (10 car., majuscule, minuscule, chiffre, spécial) |
test_siret.py | 12 | Format (14 chiffres, nettoyage espaces/tirets), algorithme de Luhn (SIRET valides/invalides), workflow complet de validation |
test_constants.py | 10 | Taux TVA (20 %), limite poids (85 %), validité devis (30 j), couleurs et emojis des statuts, couleurs des rôles |
test_repositories.py | 25 | CRUD AuthRepo (vendeur/client), création et clôture d'affaire, versioning devis, fil de commentaires, catalogue produits |
test_controllers.py | 13 | Délégation aux repositories, filtrage affaires par client, accès catalogue produits et devis |
Infrastructure de test
# conftest.py — Fixture BDD temporaire isolée par test
@pytest.fixture
def db(tmp_path):
"""Crée une base temporaire pour chaque test, nettoyée automatiquement."""
db_name = str(tmp_path / "test.db")
manager = DatabaseManager(db_name)
yield manager
# Nettoyage automatique par tmp_path
# Intégration continue : GitHub Actions (Python 3.12 + 3.13)
# Exécution : python -m pytest tests/ -vTechnologies et dépendances
Stack technique détaillé
| Composant | Technologie | Version / Rôle |
|---|---|---|
| Langage principal | Python | 3.12+ — Langage backend, logique métier et accès données |
| Interface graphique | PyQt6 | 6.x — Framework UI desktop cross-platform avec style Fusion et thème sombre |
| Base de données | SQLite 3 | Base relationnelle embarquée, 11 tables, migrations automatiques |
| Génération PDF | fpdf2 | Création de documents PDF professionnels avec tableaux et images |
| Sécurité | hashlib + hmac | PBKDF2-HMAC-SHA256 (100k itérations), comparaison temps constant |
| Validation SIRET | urllib + API gouv.fr | Vérification format + Luhn + recherche-entreprises.api.gouv.fr |
| Tests | pytest | 71 tests unitaires et d'intégration avec fixture BDD temporaire |
| Intégration continue | GitHub Actions | CI automatisée sur Python 3.12 et 3.13 |
| Persistance session | JSON | Stockage local par rôle (session_client.json / session_vendeur.json) |
Installation et exécution
# Installation des dépendances pip install -r requirements.txt # Lancement du launcher (choix Client/Vendeur) python main.py # Ou lancement direct python client_app.py # Espace Client (thème bleu) python vendeur_app.py # Espace Vendeur (thème rouge) # Exécution des tests python -m pytest tests/ -v
Compétences techniques mobilisées
| Domaine | Compétences |
|---|---|
| Python avancé | POO (classes, héritage, composition via Repository/Facade), modules et packages, gestion de fichiers, manipulation de dates (datetime), hachage sécurisé (hashlib, PBKDF2, hmac), sérialisation JSON, appels API REST (urllib) |
| Architecture logicielle | Design patterns (MVC, Repository, Facade, Template Method, Observer), séparation des responsabilités en couches (models → controllers → views), principes SOLID, modularisation stricte du code |
| Interface graphique | PyQt6 : widgets natifs et personnalisés (ProduitConfigWidget, TelephoneWidget, PasswordStrengthLabel), layouts (QVBoxLayout, QHBoxLayout, QSplitter), signaux/slots, styles QSS, palettes sombres par application, QTimer |
| Base de données | SQLite3 : DDL (CREATE TABLE, clés étrangères, contraintes UNIQUE), DML (SELECT, INSERT, UPDATE, DELETE), jointures, requêtes paramétrées, modélisation relationnelle (11 tables, relations M2M), migrations progressives |
| Sécurité | PBKDF2-HMAC-SHA256 (100k itérations, sel 32 octets), comparaison temps constant, validation de force mot de passe, requêtes SQL paramétrées (anti-injection), validation SIRET (format + Luhn + API gouvernementale) |
| Génération PDF | fpdf2 : mise en page professionnelle, tableaux avec bordures et couleurs, insertion d'images, gestion des polices, en-têtes/pieds de page récurrents, calculs automatiques HT/TVA/TTC |
| Tests et qualité | pytest : 71 tests unitaires et d'intégration, fixtures avec BDD temporaire, tests de non-régression, intégration continue GitHub Actions (Python 3.12/3.13) |
| UX/UI Design | Conception d'interfaces intuitives, thèmes sombres par rôle (bleu/rouge/violet), feedback visuel (emojis statuts, barres de progression poids, couleurs par rôle), rafraîchissement automatique |
| Gestion de projet | Analyse du besoin métier, rédaction de documentation (README, CHANGELOG versionné), développement itératif (v1.0.0 → v1.1.0), gestion de version Git avec CI |
Résultats et bénéfices
- Gain de temps — Réduction significative du temps de création des devis grâce à l'automatisation des calculs et de la génération PDF
- Négociation structurée — Système de statuts par option (en attente / accepté / refusé / contre-proposition) permettant une négociation précise et traçable sur chaque ligne du devis
- Traçabilité complète — Historique exhaustif de toutes les versions de devis et échanges, versioning automatique et commentaires horodatés
- Sécurité renforcée — Authentification PBKDF2, validation SIRET sur 3 niveaux, requêtes paramétrées, mots de passe robustes
- Qualité logicielle — 71 tests automatisés couvrant toutes les couches (auth, SIRET, constantes, repositories, contrôleurs), intégration continue
- Image professionnelle — Devis PDF standardisés avec en-tête, tableaux formatés, statuts par option et mentions légales