← Retour aux projets
Projet E6

Application de Gestion de Devis & Affaires

Application desktop de gestion de devis et d'affaires avec génération PDF.

PythonTkinterSQLite

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 vendeur
Diagramme 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
TableDescriptionColonnes principales
clientEntreprises clientes avec coordonnées, SIRET et authentificationid, nom_societe, siret, contact_nom, contact_prenom, contact_service, contact_email, contact_telephone, indicatif_tel, username, password_hash
vendeurComptes vendeurs avec authentification sécuriséeid, username, password_hash, nom, prenom, email, date_creation
affaireAffaires commerciales avec numérotation automatique (format AANNNN)id, numero_affaire, client_id, titre, description, statut, date_creation, date_modification
devisDevis versionnés (V1, V2…) rattachés à une affaireid, affaire_id, version, date_creation, total_estime, statut, notes
produitCatalogue des 5 équipements industriels avec charge max et prix de baseid, nom, charge_max, prix_base
optionOptions et accessoires (4 universelles + 20 spécifiques liées par produit)id, nom, prix, poids, universelle
produit_optionLiaison many-to-many entre produits et options spécifiquesid, produit_id, option_id (UNIQUE)
produit_devisProduits inclus dans un devis avec quantité et prixid, devis_id, produit_id, quantite, prix_unitaire
ligne_option_produit_devisOptions standard d'un produit dans un devis, avec double statut vendeur/acheteurid, produit_devis_id, option_id, statut_vendeur, prix_propose, commentaire_vendeur, statut_acheteur, commentaire_acheteur
option_personnaliseeDemandes spéciales (options sur-mesure) avec négociation prix/poidsid, produit_devis_id, description, prix_demande, poids, statut_vendeur, prix_propose, commentaire_vendeur, statut_acheteur, commentaire_acheteur
commentaireFil de discussion entre client et vendeur, rattaché à une affaireid, 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 secondes

Sé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
FichierNb testsCouverture
test_auth.py11Format 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.py12Format (14 chiffres, nettoyage espaces/tirets), algorithme de Luhn (SIRET valides/invalides), workflow complet de validation
test_constants.py10Taux TVA (20 %), limite poids (85 %), validité devis (30 j), couleurs et emojis des statuts, couleurs des rôles
test_repositories.py25CRUD AuthRepo (vendeur/client), création et clôture d'affaire, versioning devis, fil de commentaires, catalogue produits
test_controllers.py13Dé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/ -v

Technologies et dépendances

Stack technique détaillé
ComposantTechnologieVersion / Rôle
Langage principalPython3.12+ — Langage backend, logique métier et accès données
Interface graphiquePyQt66.x — Framework UI desktop cross-platform avec style Fusion et thème sombre
Base de donnéesSQLite 3Base relationnelle embarquée, 11 tables, migrations automatiques
Génération PDFfpdf2Création de documents PDF professionnels avec tableaux et images
Sécuritéhashlib + hmacPBKDF2-HMAC-SHA256 (100k itérations), comparaison temps constant
Validation SIRETurllib + API gouv.frVérification format + Luhn + recherche-entreprises.api.gouv.fr
Testspytest71 tests unitaires et d'intégration avec fixture BDD temporaire
Intégration continueGitHub ActionsCI automatisée sur Python 3.12 et 3.13
Persistance sessionJSONStockage 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

DomaineCompé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 logicielleDesign patterns (MVC, Repository, Facade, Template Method, Observer), séparation des responsabilités en couches (models → controllers → views), principes SOLID, modularisation stricte du code
Interface graphiquePyQt6 : widgets natifs et personnalisés (ProduitConfigWidget, TelephoneWidget, PasswordStrengthLabel), layouts (QVBoxLayout, QHBoxLayout, QSplitter), signaux/slots, styles QSS, palettes sombres par application, QTimer
Base de donnéesSQLite3 : 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 PDFfpdf2 : 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 DesignConception 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 projetAnalyse 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