# MODULE PÉRIPHÉRIQUES - Spécifications Complètes ## Linux BenchTools - Extension Inventaire Matériel **Version**: 1.0 **Date**: 2025-12-30 **Auteur**: Analyse collaborative **Projet**: serv_benchmark - Module Périphériques --- ## 📋 TABLE DES MATIÈRES 1. [Vue d'ensemble](#vue-densemble) 2. [Objectifs et Portée](#objectifs-et-portée) 3. [Types de Périphériques](#types-de-périphériques) 4. [Caractéristiques des Données](#caractéristiques-des-données) 5. [Architecture Base de Données](#architecture-base-de-données) 6. [Architecture Backend](#architecture-backend) 7. [Architecture Frontend](#architecture-frontend) 8. [Fonctionnalités Principales](#fonctionnalités-principales) 9. [Système de Localisation](#système-de-localisation) 10. [Système de Prêt](#système-de-prêt) 11. [Appareils Complets](#appareils-complets) 12. [Configuration YAML](#configuration-yaml) 13. [Plan de Déploiement](#plan-de-déploiement) 14. [Prompt de Développement](#prompt-de-développement) --- ## 🎯 VUE D'ENSEMBLE ### Contexte Le projet **Linux BenchTools** est une application web existante permettant de : - Benchmarker des machines Linux (CPU, RAM, Disque, Réseau, GPU) - Stocker l'historique des benchmarks - Gérer un inventaire de machines (devices) ### Besoin Ajouter un **module complet de gestion d'inventaire de périphériques** pour : - Cataloguer tous les périphériques informatiques (USB, Bluetooth, PCIe, câbles, etc.) - Gérer leur localisation physique (pièces, placards, tiroirs) - Suivre les prêts/emprunts - Lier certains périphériques aux machines benchmarkées - Gérer stock, photos, documents, garanties ### Architecture Existante **Backend**: - FastAPI + SQLAlchemy ORM - Base de données: SQLite (`data.db`) - Structure: `/backend/app/` avec models/, schemas/, api/, core/ - Authentification par token **Frontend**: - Vanilla JavaScript (pas de framework) - Thème: Monokai dark - Layout: Two-panel design - Pages: Dashboard, Devices, Settings **Base de données existante** (`data.db`): - `devices` - Machines inventoriées - `hardware_snapshots` - Snapshots matériel - `benchmarks` - Résultats benchmarks - `documents` - Documents devices - `manufacturer_links` - Liens --- ## 🎯 OBJECTIFS ET PORTÉE ### Objectifs Principaux 1. **Inventaire Complet** - Cataloguer TOUS les périphériques informatiques possibles - USB, Bluetooth, Wi-Fi, PCIe, HDMI, Câbles, Visserie, etc. - Appareils complets (Laptops, Desktops, Smartphones, Tablettes, Serveurs, Consoles) 2. **Gestion Physique** - Localisation précise (Pièce > Placard > Tiroir > Étagère) - Tracking des déplacements - QR codes pour identification rapide 3. **Gestion de Prêt** - Prêter périphériques à des personnes - Suivre les retours - Gérer cautions - Alertes et rappels automatiques 4. **Intégration Benchmarks** - Lier appareils complets aux devices benchmarkés - Afficher historique benchmarks - Sync automatique des specs 5. **Gestion Avancée** - Stock et quantités - Photos et documents - Évaluation qualité (0-5 étoiles) - Configuration YAML flexible - Compression d'images automatique ### Architecture **BASE DE DONNÉES SÉPARÉE**: `peripherals.db` - Isolation des données - Performance optimale - Backups indépendants - Module activable/désactivable --- ## 📦 TYPES DE PÉRIPHÉRIQUES ### 1. CONNECTIVITÉ FILAIRE #### USB (Universal Serial Bus) - **Types**: USB Type-A (2.0, 3.0, 3.1, 3.2), USB Type-C, Thunderbolt 3/4, Mini/Micro - **Périphériques**: - Clés USB / Disques externes - Souris / Claviers - Webcams - Imprimantes / Scanners - Adaptateurs réseau (Ethernet, Wi-Fi) - Cartes son externes / DAC - Dongles (Bluetooth, Wi-Fi, DVB-T) - Lecteurs de cartes - Contrôleurs de jeux - Microphones / Interfaces audio - Hubs USB #### PCIe (Peripheral Component Interconnect Express) - Cartes graphiques (GPU) - Cartes son - Cartes réseau (Ethernet, Wi-Fi, Fibre) - Cartes d'acquisition vidéo - Contrôleurs RAID - Adaptateurs NVMe - Cartes USB/Thunderbolt d'extension - Cartes TV/DVB #### SATA / M.2 / NVMe - Disques durs (HDD) - SSD SATA - SSD M.2 (SATA/NVMe) - Lecteurs optiques (CD/DVD/Blu-ray) #### Audio / Vidéo - **Audio**: Jack 3.5mm/6.35mm, XLR, Optique S/PDIF, Coaxial - **Vidéo**: HDMI (1.4, 2.0, 2.1), DisplayPort, DVI, VGA, Mini DisplayPort - **Périphériques**: Écrans, Projecteurs, Casques, Enceintes, Microphones ### 2. CONNECTIVITÉ SANS-FIL #### Wi-Fi - Normes: 802.11 a/b/g/n/ac/ax (Wi-Fi 4/5/6/6E/7) - Bandes: 2.4 GHz, 5 GHz, 6 GHz - Adaptateurs USB, Cartes PCIe/M.2, Points d'accès #### Bluetooth - Versions: 2.0 à 5.3 - Profils: A2DP, HFP, HSP, HID - Casques, Claviers, Souris, Enceintes, Adaptateurs #### Autres - NFC, RF propriétaire, Infrarouge ### 3. RÉSEAU #### Ethernet - Vitesses: 10/100/1000 Mbps, 2.5G, 5G, 10G, 25G, 40G - Cartes NIC, Switches, Routeurs, Modems #### Fibre Optique - Cartes SFP/SFP+, Convertisseurs ### 4. CÂBLES #### Types de Câbles - **HDMI**: Versions 1.4, 2.0, 2.1 (Standard, Premium, Ultra) - **DisplayPort**: Versions 1.2, 1.4, 2.0 - **USB**: USB-A, USB-C, Mini, Micro (différentes longueurs) - **Ethernet**: Cat5e, Cat6, Cat6a, Cat7, Cat8 (blindé/non-blindé) - **Audio**: Jack 3.5mm, XLR, RCA, Optique - **Alimentation**: C13, C14, Secteur, USB-C PD - **Vidéo**: DVI, VGA, Composite **Caractéristiques spécifiques**: - Longueur (mètres) - Certification (pour HDMI) - Blindage (pour Ethernet) - Puissance supportée (pour USB-C) ### 5. VISSERIE ET FIXATIONS #### Types - **Vis**: M2, M3, M4 (différentes longueurs, têtes: plate, fraisée, bombée) - **Entretoises (Standoffs)**: M2, M3, M4 (différentes hauteurs) - **Écrous**, **Rondelles** - **Clips plastiques**, **Attaches câbles** **Caractéristiques**: - Diamètre/Norme - Longueur - Matériau (Acier, Inox, Laiton, Plastique) - Type de tête ### 6. BOÎTIERS PC #### Formats - Mini-ITX - Micro-ATX - ATX - E-ATX - Full Tower **Caractéristiques**: - Emplacements 3.5" / 2.5" - Ventilateurs inclus/supportés - Fenêtre latérale - RGB - Dimensions ### 7. STOCKAGE - Disques durs externes - SSD externes - NAS (Network Attached Storage) - Lecteurs de bandes - Stations d'accueil ### 8. ENTRÉE / SORTIE #### Entrée - Claviers (USB, Bluetooth, PS/2) - Souris / Trackballs / Tablettes graphiques - Scanners - Lecteurs de codes-barres - Lecteurs biométriques - Webcams - Microphones #### Sortie - Imprimantes (USB, réseau, sans-fil) - Imprimantes 3D - Enceintes - Projecteurs ### 9. APPAREILS COMPLETS #### Desktop (PC de Bureau) **Caractéristiques**: - CPU (marque, modèle, cores, threads, fréquence) - RAM (capacité, type DDR, fréquence) - GPU (intégré/dédié, modèle, VRAM) - Stockage (type, capacité) - Carte mère (marque, modèle, chipset) - Alimentation (wattage, certification 80+) - Boîtier (format, modèle) - Refroidissement (Air/AIO/Watercooling) - Système d'exploitation #### Laptop (Ordinateur Portable) **Caractéristiques**: - Écran (taille, résolution, tactile, taux rafraîchissement) - CPU (marque, modèle, génération) - RAM (capacité, extensible, max) - Stockage (type, capacité, slots M.2 libres) - GPU (intégré/dédié/hybrid) - Batterie (Wh, autonomie) - Connectique (USB-A, USB-C, Thunderbolt, HDMI, Ethernet, SD) - Webcam (résolution) - Poids #### Tablette **Caractéristiques**: - OS (iOS/iPadOS, Android, Windows) - Écran (taille, résolution, type: LCD/OLED) - Processeur - RAM, Stockage - Batterie (mAh) - Stylet (inclus, modèle) - Clavier (inclus) - 4G/5G - Poids #### Smartphone **Caractéristiques**: - OS (iOS, Android) - Écran (taille, résolution, type, fréquence Hz) - Processeur - RAM, Stockage - Caméras (principale MP, ultra-wide, téléphoto, frontale) - Batterie (mAh, charge rapide W, sans-fil) - 5G, Dual SIM, eSIM - Résistance (IP67/IP68) - Lecteur d'empreintes - IMEI #### Serveur **Caractéristiques**: - Format (Tour, Rack 1U/2U/4U, Blade) - OS (Windows Server, Linux, ESXi, Proxmox) - CPU (nombre, modèle, cores total) - RAM (capacité, type ECC) - RAID (contrôleur, niveau) - Baies (3.5", 2.5") - Alimentation (redondante, wattage) - IPMI/iLO/iDRAC #### Console de Jeu **Caractéristiques**: - Type: Console de salon, Portable, Hybrid - Marque: PlayStation, Xbox, Nintendo, Steam, Autre - Modèle: PS5, Xbox Series X, Switch OLED, Steam Deck, etc. - Génération: PS5 (9ème gen), PS4 (8ème gen), etc. - Stockage (capacité, type, extensible) - Résolution max (1080p, 4K, 8K) - FPS max - Ray tracing - VR compatible - Lecteur disque physique - Rétrocompatibilité - Manettes incluses - Connectique (HDMI version, USB, Ethernet) - Dimensions - Poids **Exemple de configuration YAML**: ```yaml - id: console_gaming nom: "Console de Jeu" icon: "gamepad" is_complete_device: true champs_specifiques: - type_console: type: "select" options: ["Salon", "Portable", "Hybrid"] label: "Type de console" - marque: type: "select" options: ["PlayStation", "Xbox", "Nintendo", "Steam", "Autre"] - modele: type: "text" placeholder: "ex: PlayStation 5, Xbox Series X, Switch OLED" - generation: type: "text" label: "Génération" - stockage_go: type: "number" label: "Stockage (Go)" - stockage_type: type: "select" options: ["SSD", "HDD", "Cartouche"] - stockage_extensible: type: "boolean" - resolution_max: type: "select" options: ["720p", "1080p", "1440p", "4K", "8K"] - fps_max: type: "number" label: "FPS maximum" - ray_tracing: type: "boolean" - vr_compatible: type: "boolean" label: "Compatible VR" - lecteur_disque: type: "boolean" label: "Lecteur disque physique" - retrocompatibilite: type: "text" label: "Rétrocompatibilité" placeholder: "ex: PS4, Xbox One" - manettes_incluses: type: "number" label: "Manettes incluses" - hdmi_version: type: "select" options: ["1.4", "2.0", "2.1"] - ports_usb: type: "number" - ethernet_gbps: type: "select" options: ["1", "2.5", "10"] - wifi: type: "select" options: ["Wi-Fi 5", "Wi-Fi 6", "Wi-Fi 6E"] - bluetooth: type: "select" options: ["4.0", "5.0", "5.1", "5.2"] - poids_kg: type: "number" step: 0.1 ``` ### 10. AUTRES - Onduleurs (UPS) - Alimentations PC - Cartes d'acquisition (DAQ) - Dispositifs KVM - Contrôleurs MIDI - GPS USB - Dongles de sécurité --- ## 🔧 CARACTÉRISTIQUES DES DONNÉES ### Caractéristiques GLOBALES (tous périphériques) ``` 📝 IDENTIFICATION ├── id (auto-increment) ├── nom (user-defined name) ├── type_principal (USB, Bluetooth, PCIe, etc.) ├── sous_type (Clé USB, Souris, GPU, etc.) ├── marque (manufacturer) ├── modele (model number) ├── numero_serie (serial number) └── ean_upc (barcode) 💰 ACHAT ├── boutique (store name) ├── date_achat (purchase date) ├── prix (price) ├── devise (currency, default: EUR) ├── garantie_duree_mois (warranty duration) └── garantie_expiration (warranty expiration date) ⭐ ÉVALUATION ├── rating (0-5 étoiles, float) └── notes (free text review) 📦 STOCK ├── quantite_totale (total quantity) ├── quantite_disponible (available) ├── seuil_alerte (alert threshold) └── emplacement_stockage (storage location) 📷 MÉDIAS ├── photos[] (array of image paths) ├── pdf_manuels[] (manuals) ├── pdf_factures[] (invoices) └── pdf_autres[] (other docs) 🔗 LIENS & RÉFÉRENCES ├── url_fabricant (manufacturer page) ├── url_support (support page) ├── url_drivers (drivers download) ├── url_documentation └── liens_personnalises[] (custom links) 📋 MÉTADONNÉES ├── date_creation (record creation) ├── date_modification (last update) ├── etat (Neuf, Bon, Usagé, Défectueux, Retiré) ├── localisation (physical location) ├── proprietaire (owner) ├── tags[] (custom tags, JSON) └── notes (free text) 🐧 IDENTIFICATION LINUX ├── device_path (e.g., /dev/sda, /dev/input/mouse0) ├── sysfs_path (e.g., /sys/devices/pci0000:00/...) ├── vendor_id (USB: VID, PCI: vendor ID) ├── product_id (USB: PID, PCI: device ID) ├── class_id (device class) ├── driver_utilise (kernel driver in use) ├── modules_kernel[] (required kernel modules, JSON) ├── udev_rules (custom udev rules) └── identifiant_systeme (lsusb, lspci output) ⚙️ INSTALLATION & PROBLÈMES ├── installation_auto (auto-detected: bool) ├── driver_requis (required drivers) ├── firmware_requis (required firmware) ├── paquets_necessaires[] (required packages, JSON) ├── commandes_installation (installation commands) ├── problemes_connus (known issues) ├── solutions (solutions/workarounds) └── compatibilite_noyau (kernel version compatibility) 🔌 CONNECTIVITÉ ├── interface_connexion (USB, PCIe, Bluetooth, etc.) ├── connecte_a (connected to which device/port) └── consommation_electrique_w (power consumption) 📍 LOCALISATION PHYSIQUE ├── location_id (FK vers locations) ├── location_details (détails supplémentaires) └── location_auto (bool: suit le device assigné) 🤝 PRÊT / EMPRUNT ├── en_pret (bool) ├── pret_actuel_id (FK vers peripheral_loans) └── prete_a (nom de l'emprunteur, pour affichage rapide) 💻 APPAREIL COMPLET ├── is_complete_device (bool) ├── device_type (desktop, laptop, tablet, smartphone, server, console) ├── linked_device_id (→ devices.id dans data.db, pour benchmarks) └── device_id (→ devices.id dans data.db, assignation actuelle) 🎨 DONNÉES SPÉCIFIQUES └── caracteristiques_specifiques (JSON flexible par type) ``` ### Caractéristiques SPÉCIFIQUES (exemples par type) #### Stockage USB ```json { "capacite_go": 128, "type_memoire": "Flash NAND", "usb_version": "3.2 Gen 1", "vitesse_lecture_mbs": 150, "vitesse_ecriture_mbs": 50, "systeme_fichiers": "exFAT", "chiffrement": true, "chiffrement_type": "AES 256-bit" } ``` #### Souris ```json { "connexion": "Bluetooth 5.0", "capteur_type": "Optique", "dpi_max": 16000, "dpi_reglable": true, "nombre_boutons": 8, "boutons_programmables": 6, "autonomie_heures": 200, "type_batterie": "Rechargeable Li-ion", "eclairage_rgb": true } ``` #### Câble HDMI ```json { "longueur_m": 2, "version_hdmi": "2.1", "certification": "Premium", "debit_max_gbps": 48, "support_4k_120hz": true, "support_8k": true, "arc_earc": "eARC" } ``` #### Desktop PC ```json { "systeme_exploitation": "Windows 11 Pro", "cpu_marque": "Intel", "cpu_modele": "Core i7-12700K", "cpu_cores": 12, "cpu_threads": 20, "ram_total_go": 32, "ram_type": "DDR5", "ram_freq_mhz": 5600, "gpu_type": "Dédié", "gpu_modele": "NVIDIA RTX 4070", "gpu_vram_go": 12, "stockage_principal": "1TB NVMe SSD", "carte_mere_marque": "ASUS", "carte_mere_modele": "ROG STRIX Z690-F", "alimentation_w": 850, "alimentation_certification": "80+ Gold", "boitier_format": "ATX", "refroidissement": "AIO 280mm" } ``` #### Console de Jeu ```json { "type_console": "Salon", "marque": "PlayStation", "modele": "PlayStation 5", "generation": "9ème génération", "stockage_go": 825, "stockage_type": "SSD", "stockage_extensible": true, "resolution_max": "4K", "fps_max": 120, "ray_tracing": true, "vr_compatible": true, "lecteur_disque": true, "retrocompatibilite": "PS4", "manettes_incluses": 1, "hdmi_version": "2.1", "ports_usb": 4, "ethernet_gbps": "1", "wifi": "Wi-Fi 6", "bluetooth": "5.1", "poids_kg": 4.5 } ``` --- ## 🗄️ ARCHITECTURE BASE DE DONNÉES ### Stratégie: Deux Bases de Données Séparées ``` /backend/data/ ├── data.db # DB PRINCIPALE (existante) │ ├── devices # Machines benchmarkées │ ├── hardware_snapshots # Snapshots hardware │ ├── benchmarks # Résultats benchmarks │ ├── documents # Documents devices │ ├── manufacturer_links # Liens devices │ └── disk_smart # Données SMART │ └── peripherals.db # DB PÉRIPHÉRIQUES (nouvelle) ├── peripherals # Périphériques ├── peripheral_photos # Photos ├── peripheral_documents # Documents ├── peripheral_links # Liens ├── peripheral_loans # Prêts ├── peripheral_location_history # Historique déplacements ├── peripheral_attachments # Relations périph ↔ périph ├── locations # Emplacements physiques └── borrower_blacklist # Liste noire emprunteurs ``` ### Pourquoi Séparé ? ✅ **Isolation** - Données indépendantes ✅ **Performance** - Pas de contention ✅ **Scalabilité** - Peut grossir indépendamment ✅ **Backups** - Stratégies distinctes ✅ **Migration** - Plus facile ✅ **Modularité** - Activable/désactivable ### Schéma Table `peripherals` ```sql CREATE TABLE peripherals ( -- IDENTIFICATION id INTEGER PRIMARY KEY AUTOINCREMENT, nom VARCHAR(255) NOT NULL, type_principal VARCHAR(100) NOT NULL, sous_type VARCHAR(100), marque VARCHAR(100), modele VARCHAR(255), numero_serie VARCHAR(255), ean_upc VARCHAR(50), -- ACHAT boutique VARCHAR(255), date_achat DATE, prix DECIMAL(10, 2), devise VARCHAR(10) DEFAULT 'EUR', garantie_duree_mois INTEGER, garantie_expiration DATE, -- ÉVALUATION rating FLOAT DEFAULT 0, -- 0-5 étoiles -- STOCK quantite_totale INTEGER DEFAULT 1, quantite_disponible INTEGER DEFAULT 1, seuil_alerte INTEGER DEFAULT 0, -- MÉTADONNÉES date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP, date_modification TIMESTAMP DEFAULT CURRENT_TIMESTAMP, etat VARCHAR(50) DEFAULT 'Neuf', localisation VARCHAR(255), proprietaire VARCHAR(100), tags TEXT, -- JSON array notes TEXT, -- LINUX IDENTIFICATION device_path VARCHAR(255), sysfs_path VARCHAR(500), vendor_id VARCHAR(20), product_id VARCHAR(20), class_id VARCHAR(20), driver_utilise VARCHAR(100), modules_kernel TEXT, -- JSON udev_rules TEXT, identifiant_systeme TEXT, -- INSTALLATION installation_auto BOOLEAN DEFAULT FALSE, driver_requis TEXT, firmware_requis TEXT, paquets_necessaires TEXT, -- JSON commandes_installation TEXT, problemes_connus TEXT, solutions TEXT, compatibilite_noyau VARCHAR(100), -- CONNECTIVITÉ interface_connexion VARCHAR(100), connecte_a VARCHAR(255), consommation_electrique_w DECIMAL(6, 2), -- LOCALISATION PHYSIQUE location_id INTEGER, location_details VARCHAR(500), location_auto BOOLEAN DEFAULT TRUE, -- PRÊT en_pret BOOLEAN DEFAULT FALSE, pret_actuel_id INTEGER, prete_a VARCHAR(255), -- APPAREIL COMPLET is_complete_device BOOLEAN DEFAULT FALSE, device_type VARCHAR(50), -- LIEN VERS DB PRINCIPALE (logique, pas FK SQL) linked_device_id INTEGER, -- → devices.id dans data.db (benchmarks) device_id INTEGER, -- → devices.id dans data.db (assignation) -- DONNÉES SPÉCIFIQUES caracteristiques_specifiques TEXT -- JSON ); CREATE INDEX idx_peripherals_type ON peripherals(type_principal); CREATE INDEX idx_peripherals_sous_type ON peripherals(sous_type); CREATE INDEX idx_peripherals_marque ON peripherals(marque); CREATE INDEX idx_peripherals_device ON peripherals(device_id); CREATE INDEX idx_peripherals_etat ON peripherals(etat); CREATE INDEX idx_peripherals_complete_device ON peripherals(is_complete_device); CREATE INDEX idx_peripherals_en_pret ON peripherals(en_pret); ``` ### Table `peripheral_photos` ```sql CREATE TABLE peripheral_photos ( id INTEGER PRIMARY KEY AUTOINCREMENT, peripheral_id INTEGER NOT NULL, filename VARCHAR(255) NOT NULL, stored_path VARCHAR(500) NOT NULL, mime_type VARCHAR(100), size_bytes INTEGER, uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, description TEXT, is_primary BOOLEAN DEFAULT FALSE, FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE ); CREATE INDEX idx_peripheral_photos_pid ON peripheral_photos(peripheral_id); ``` ### Table `peripheral_documents` ```sql CREATE TABLE peripheral_documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, peripheral_id INTEGER NOT NULL, doc_type VARCHAR(50) NOT NULL, -- manual, warranty, invoice, datasheet, other filename VARCHAR(255) NOT NULL, stored_path VARCHAR(500) NOT NULL, mime_type VARCHAR(100), size_bytes INTEGER, uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, description TEXT, FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE ); CREATE INDEX idx_peripheral_documents_pid ON peripheral_documents(peripheral_id); CREATE INDEX idx_peripheral_documents_type ON peripheral_documents(doc_type); ``` ### Table `peripheral_links` ```sql CREATE TABLE peripheral_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, peripheral_id INTEGER NOT NULL, link_type VARCHAR(50) NOT NULL, -- manufacturer, support, drivers, documentation, custom label VARCHAR(255) NOT NULL, url TEXT NOT NULL, FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE ); CREATE INDEX idx_peripheral_links_pid ON peripheral_links(peripheral_id); ``` ### Table `peripheral_loans` ```sql CREATE TABLE peripheral_loans ( id INTEGER PRIMARY KEY AUTOINCREMENT, peripheral_id INTEGER NOT NULL, -- Emprunteur emprunte_par VARCHAR(255) NOT NULL, email_emprunteur VARCHAR(255), telephone VARCHAR(50), -- Dates date_pret DATE NOT NULL, date_retour_prevue DATE NOT NULL, date_retour_effectif DATE, -- Statut statut VARCHAR(50) NOT NULL DEFAULT 'en_cours', -- en_cours, retourne, en_retard -- Caution caution_montant DECIMAL(10, 2), caution_rendue BOOLEAN DEFAULT FALSE, -- État etat_depart VARCHAR(50), etat_retour VARCHAR(50), problemes_retour TEXT, -- Informations raison_pret TEXT, notes TEXT, created_by VARCHAR(100), -- Rappels rappel_envoye BOOLEAN DEFAULT FALSE, date_rappel TIMESTAMP, FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE ); CREATE INDEX idx_loans_peripheral ON peripheral_loans(peripheral_id); CREATE INDEX idx_loans_statut ON peripheral_loans(statut); CREATE INDEX idx_loans_emprunteur ON peripheral_loans(emprunte_par); CREATE INDEX idx_loans_retour_prevue ON peripheral_loans(date_retour_prevue); ``` ### Table `locations` ```sql CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, nom VARCHAR(255) NOT NULL UNIQUE, type VARCHAR(50) NOT NULL, -- piece, placard, tiroir, etagere, meuble, boite parent_id INTEGER, -- Hiérarchie description TEXT, image_path VARCHAR(500), qr_code_path VARCHAR(500), ordre_affichage INTEGER DEFAULT 0, FOREIGN KEY (parent_id) REFERENCES locations(id) ON DELETE CASCADE ); CREATE INDEX idx_locations_parent ON locations(parent_id); CREATE INDEX idx_locations_type ON locations(type); ``` ### Table `peripheral_location_history` ```sql CREATE TABLE peripheral_location_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, peripheral_id INTEGER NOT NULL, from_location_id INTEGER, to_location_id INTEGER, from_device_id INTEGER, to_device_id INTEGER, action VARCHAR(50) NOT NULL, -- moved, assigned, unassigned, stored timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, notes TEXT, user VARCHAR(100), FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE, FOREIGN KEY (from_location_id) REFERENCES locations(id) ON DELETE SET NULL, FOREIGN KEY (to_location_id) REFERENCES locations(id) ON DELETE SET NULL ); CREATE INDEX idx_peripheral_history_pid ON peripheral_location_history(peripheral_id); ``` ### Organisation Fichiers Uploads ``` /uploads/peripherals/ ├── photos/ │ ├── {hash16}_{peripheral_id}_1.jpg │ ├── {hash16}_{peripheral_id}_2.png │ └── ... ├── documents/ │ ├── manuals/ │ │ └── {hash16}_{peripheral_id}_manual.pdf │ ├── invoices/ │ │ └── {hash16}_{peripheral_id}_invoice.pdf │ ├── warranties/ │ │ └── {hash16}_{peripheral_id}_warranty.pdf │ └── datasheets/ │ └── {hash16}_{peripheral_id}_datasheet.pdf └── thumbnails/ └── {hash16}_{peripheral_id}_thumb.webp ``` --- ## ⚙️ ARCHITECTURE BACKEND ### Configuration (`/backend/app/core/config.py`) ```python class Settings(BaseSettings): # ... config existante ... # DATABASE PRINCIPALE (benchmarks) DATABASE_URL: str = os.getenv( "DATABASE_URL", "sqlite:///./backend/data/data.db" ) # DATABASE PÉRIPHÉRIQUES (nouvelle) PERIPHERALS_DB_URL: str = os.getenv( "PERIPHERALS_DB_URL", "sqlite:///./backend/data/peripherals.db" ) # Upload directories UPLOAD_DIR: str = "./uploads" PERIPHERALS_UPLOAD_DIR: str = "./uploads/peripherals" # Module peripherals enabled/disabled PERIPHERALS_MODULE_ENABLED: bool = os.getenv( "PERIPHERALS_MODULE_ENABLED", "true" ).lower() == "true" # Image compression IMAGE_COMPRESSION_ENABLED: bool = True IMAGE_COMPRESSION_QUALITY: int = 85 IMAGE_MAX_WIDTH: int = 1920 IMAGE_MAX_HEIGHT: int = 1080 THUMBNAIL_SIZE: int = 300 THUMBNAIL_QUALITY: int = 75 THUMBNAIL_FORMAT: str = "webp" ``` ### Sessions DB (`/backend/app/db/session.py`) ```python from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, Session # DB Principale engine_main = create_engine(settings.DATABASE_URL, ...) SessionLocalMain = sessionmaker(bind=engine_main) # DB Périphériques engine_peripherals = create_engine(settings.PERIPHERALS_DB_URL, ...) SessionLocalPeripherals = sessionmaker(bind=engine_peripherals) # Dependency Injection def get_db() -> Session: """Session DB principale""" db = SessionLocalMain() try: yield db finally: db.close() def get_peripherals_db() -> Session: """Session DB périphériques""" db = SessionLocalPeripherals() try: yield db finally: db.close() ``` ### Modèles SQLAlchemy **Base**: ```python # /backend/app/db/base.py Base = declarative_base() # DB principale BasePeripherals = declarative_base() # DB périphériques ``` **Modèle Peripheral**: ```python # /backend/app/models/peripheral.py from app.db.base import BasePeripherals class Peripheral(BasePeripherals): __tablename__ = "peripherals" # ... tous les champs définis plus haut ``` ### Schémas Pydantic ```python # /backend/app/schemas/peripheral.py class PeripheralBase(BaseModel): nom: str type_principal: str sous_type: Optional[str] = None marque: Optional[str] = None modele: Optional[str] = None # ... tous les champs class PeripheralCreate(PeripheralBase): pass class PeripheralUpdate(BaseModel): nom: Optional[str] = None # ... tous les champs optionnels class PeripheralDetail(PeripheralBase): id: int date_creation: datetime photos: List[PeripheralPhotoSchema] documents: List[PeripheralDocumentSchema] links: List[PeripheralLinkSchema] loan: Optional[PeripheralLoanSchema] location: Optional[LocationSchema] class Config: from_attributes = True class PeripheralSummary(BaseModel): id: int nom: str type_principal: str sous_type: Optional[str] marque: Optional[str] etat: str rating: float prix: Optional[float] photo_principale: Optional[str] en_pret: bool class Config: from_attributes = True class PeripheralListResponse(BaseModel): items: List[PeripheralSummary] total: int page: int page_size: int total_pages: int ``` ### Routes API (`/backend/app/api/peripherals.py`) ```python from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from sqlalchemy.orm import Session from app.db.session import get_db, get_peripherals_db from app.services.peripheral_service import PeripheralService router = APIRouter(prefix="/api/peripherals", tags=["peripherals"]) # CRUD Périphériques @router.get("/", response_model=PeripheralListResponse) async def list_peripherals( page: int = 1, page_size: int = 50, search: Optional[str] = None, type_principal: Optional[str] = None, marque: Optional[str] = None, etat: Optional[str] = None, en_pret: Optional[bool] = None, is_complete_device: Optional[bool] = None, db: Session = Depends(get_peripherals_db) ): """Liste périphériques avec pagination et filtres""" pass @router.get("/{peripheral_id}", response_model=PeripheralDetail) async def get_peripheral( peripheral_id: int, db_peripherals: Session = Depends(get_peripherals_db), db_main: Session = Depends(get_db) ): """Détail périphérique avec relations""" return PeripheralService.get_peripheral_with_device( peripheral_id, db_peripherals, db_main ) @router.post("/", response_model=PeripheralDetail) async def create_peripheral( peripheral: PeripheralCreate, db: Session = Depends(get_peripherals_db) ): """Créer périphérique""" pass @router.put("/{peripheral_id}") async def update_peripheral( peripheral_id: int, data: PeripheralUpdate, db: Session = Depends(get_peripherals_db) ): """Mettre à jour périphérique""" pass @router.delete("/{peripheral_id}") async def delete_peripheral( peripheral_id: int, db: Session = Depends(get_peripherals_db) ): """Supprimer périphérique""" pass # Photos @router.post("/{peripheral_id}/photos") async def upload_photo( peripheral_id: int, file: UploadFile = File(...), compress: bool = True, db: Session = Depends(get_peripherals_db) ): """Upload photo avec compression optionnelle""" pass @router.get("/{peripheral_id}/photos") async def get_photos( peripheral_id: int, db: Session = Depends(get_peripherals_db) ): """Liste photos""" pass @router.delete("/photos/{photo_id}") async def delete_photo( photo_id: int, db: Session = Depends(get_peripherals_db) ): """Supprimer photo""" pass # Documents @router.post("/{peripheral_id}/documents") async def upload_document( peripheral_id: int, file: UploadFile = File(...), doc_type: str = "other", db: Session = Depends(get_peripherals_db) ): """Upload document""" pass @router.get("/{peripheral_id}/documents") async def get_documents( peripheral_id: int, db: Session = Depends(get_peripherals_db) ): """Liste documents""" pass @router.get("/documents/{doc_id}/download") async def download_document(doc_id: int, db: Session = Depends(get_peripherals_db)): """Télécharger document""" pass # Liens @router.post("/{peripheral_id}/links") @router.get("/{peripheral_id}/links") @router.put("/links/{link_id}") @router.delete("/links/{link_id}") # Prêts @router.post("/{peripheral_id}/loan") async def create_loan( peripheral_id: int, loan: LoanCreate, db: Session = Depends(get_peripherals_db) ): """Créer un prêt""" pass @router.post("/loans/{loan_id}/return") async def return_loan( loan_id: int, return_data: LoanReturn, db: Session = Depends(get_peripherals_db) ): """Enregistrer retour""" pass @router.get("/loans/active") @router.get("/loans/overdue") @router.post("/loans/{loan_id}/reminder") # Localisation @router.post("/{peripheral_id}/assign") @router.post("/{peripheral_id}/unassign") @router.get("/{peripheral_id}/history") # Appareils complets @router.post("/{peripheral_id}/link-device") @router.delete("/{peripheral_id}/unlink-device") @router.get("/{peripheral_id}/benchmarks") # Recherche avancée @router.get("/search") async def advanced_search( q: str, filters: dict, db: Session = Depends(get_peripherals_db) ): """Recherche multi-critères""" pass # Config @router.get("/types") async def get_peripheral_types(): """Liste types depuis YAML""" pass ``` ### Service Layer (`/backend/app/services/peripheral_service.py`) ```python class PeripheralService: @staticmethod def get_peripheral_with_device( peripheral_id: int, db_peripherals: Session, db_main: Session ): """Récupère périphérique avec device lié""" peripheral = db_peripherals.query(Peripheral).get(peripheral_id) result = { "peripheral": peripheral, "linked_device": None, "benchmarks": [] } if peripheral.linked_device_id: device = db_main.query(Device).get(peripheral.linked_device_id) result["linked_device"] = device if device: result["benchmarks"] = device.benchmarks return result @staticmethod def compress_image(file_path: str, quality: int = 85): """Compresse une image""" from PIL import Image img = Image.open(file_path) # ... compression logic pass @staticmethod def generate_thumbnail(file_path: str, size: int = 300): """Génère thumbnail""" pass ``` ### Main App (`/backend/app/main.py`) ```python from app.api import peripherals, locations, loans if settings.PERIPHERALS_MODULE_ENABLED: app.include_router(peripherals.router) app.include_router(locations.router) app.include_router(loans.router) ``` --- ## 🎨 ARCHITECTURE FRONTEND ### Pages HTML ``` /frontend/ ├── peripherals.html # Page principale périphériques ├── peripheral_detail.html # Détail périphérique (optionnel) ├── locations.html # Gestion emplacements ├── loans.html # Gestion prêts └── (pages existantes) ``` ### JavaScript ``` /frontend/js/ ├── api.js # Extension BenchAPI.Peripherals.* ├── peripherals.js # Logique page principale ├── peripheral_detail.js # Logique détail ├── locations.js # Gestion emplacements ├── loans.js # Gestion prêts ├── components/ │ ├── PhotoGallery.js # Galerie photos │ ├── DocumentList.js # Liste documents │ ├── PeripheralFilters.js # Filtres avancés │ ├── LocationSelector.js # Sélecteur arborescence │ ├── RatingStars.js # Système étoiles │ ├── TypeManager.js # Gestion types YAML │ ├── StockManager.js # Gestion stock │ └── ImageUploader.js # Upload images └── utils.js # (existant, étendre si besoin) ``` ### Extension API Client (`/frontend/js/api.js`) ```javascript window.BenchAPI = window.BenchAPI || {}; window.BenchAPI.Peripherals = { // CRUD getPeripherals: async (page = 1, filters = {}) => { const params = new URLSearchParams({ page, ...filters }); return await fetch(`${API_URL}/peripherals?${params}`).then(r => r.json()); }, getPeripheral: async (id) => { return await fetch(`${API_URL}/peripherals/${id}`).then(r => r.json()); }, createPeripheral: async (data) => { return await fetch(`${API_URL}/peripherals`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()); }, updatePeripheral: async (id, data) => { return await fetch(`${API_URL}/peripherals/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()); }, deletePeripheral: async (id) => { return await fetch(`${API_URL}/peripherals/${id}`, { method: 'DELETE' }); }, // Photos uploadPhoto: async (peripheralId, file, compress = true) => { const formData = new FormData(); formData.append('file', file); formData.append('compress', compress); return await fetch(`${API_URL}/peripherals/${peripheralId}/photos`, { method: 'POST', body: formData }).then(r => r.json()); }, getPhotos: async (peripheralId) => { return await fetch(`${API_URL}/peripherals/${peripheralId}/photos`) .then(r => r.json()); }, // Documents uploadDocument: async (peripheralId, file, docType) => { const formData = new FormData(); formData.append('file', file); formData.append('doc_type', docType); return await fetch(`${API_URL}/peripherals/${peripheralId}/documents`, { method: 'POST', body: formData }).then(r => r.json()); }, // Liens addLink: async (peripheralId, link) => { return await fetch(`${API_URL}/peripherals/${peripheralId}/links`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(link) }).then(r => r.json()); }, // Prêts createLoan: async (peripheralId, loanData) => { return await fetch(`${API_URL}/peripherals/${peripheralId}/loan`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(loanData) }).then(r => r.json()); }, returnLoan: async (loanId, returnData) => { return await fetch(`${API_URL}/peripherals/loans/${loanId}/return`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(returnData) }).then(r => r.json()); }, getActiveLoans: async () => { return await fetch(`${API_URL}/peripherals/loans/active`) .then(r => r.json()); }, // Benchmarks (appareils complets) linkDevice: async (peripheralId, deviceId) => { return await fetch(`${API_URL}/peripherals/${peripheralId}/link-device`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ device_id: deviceId }) }).then(r => r.json()); }, getBenchmarks: async (peripheralId) => { return await fetch(`${API_URL}/peripherals/${peripheralId}/benchmarks`) .then(r => r.json()); }, // Recherche search: async (query, filters = {}) => { const params = new URLSearchParams({ q: query, ...filters }); return await fetch(`${API_URL}/peripherals/search?${params}`) .then(r => r.json()); }, // Config getTypes: async () => { return await fetch(`${API_URL}/peripherals/types`) .then(r => r.json()); } }; // Locations window.BenchAPI.Locations = { getLocations: async () => { return await fetch(`${API_URL}/locations`).then(r => r.json()); }, getLocationTree: async () => { return await fetch(`${API_URL}/locations/tree`).then(r => r.json()); }, createLocation: async (data) => { return await fetch(`${API_URL}/locations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()); } }; ``` ### Composants Réutilisables #### PhotoGallery.js ```javascript class PhotoGallery { constructor(containerId, peripheralId) { this.container = document.getElementById(containerId); this.peripheralId = peripheralId; this.photos = []; } async load() { this.photos = await BenchAPI.Peripherals.getPhotos(this.peripheralId); this.render(); } render() { // Grid layout avec lightbox this.container.innerHTML = `
${this.photos.map(p => `
${p.description} ${p.is_primary ? 'Principal' : ''}
`).join('')}
`; } static openLightbox(imagePath) { // Lightbox overlay } } ``` #### RatingStars.js ```javascript class RatingStars { constructor(containerId, currentRating = 0, editable = true) { this.container = document.getElementById(containerId); this.rating = currentRating; this.editable = editable; this.render(); } render() { const stars = []; for (let i = 1; i <= 5; i++) { const filled = i <= Math.round(this.rating); stars.push(` `); } this.container.innerHTML = `
${stars.join('')} ${this.rating.toFixed(1)}/5
`; } setRating(value) { this.rating = value; this.render(); // Trigger event this.container.dispatchEvent(new CustomEvent('ratingChange', { detail: { rating: value } })); } } ``` ### CSS Additions (`/frontend/css/components.css`) ```css /* Périphériques */ .peripheral-card { background: var(--card-bg); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; transition: transform 0.2s; } .peripheral-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.3); } .peripheral-header { display: flex; justify-content: space-between; align-items: center; } .peripheral-type-badge { background: var(--color-info); color: #000; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.875rem; } .peripheral-status { display: flex; gap: 0.5rem; } .status-badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; } .status-neuf { background: #a6e22e; color: #000; } .status-bon { background: #66d9ef; color: #000; } .status-use { background: #fd971f; color: #000; } .status-defectueux { background: #f92672; color: #fff; } /* Rating Stars */ .rating-stars { display: flex; align-items: center; gap: 0.25rem; } .star { font-size: 1.5rem; color: #666; cursor: pointer; transition: color 0.2s; } .star.filled { color: #ffd700; } .star:hover { color: #ffd700; } /* Photo Gallery */ .photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1rem; } .photo-item { position: relative; aspect-ratio: 1; border-radius: 8px; overflow: hidden; cursor: pointer; } .photo-item img { width: 100%; height: 100%; object-fit: cover; } .photo-upload { border: 2px dashed var(--border-color); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: border-color 0.2s; } .photo-upload:hover { border-color: var(--color-info); } /* Location Tree */ .location-tree { list-style: none; padding-left: 0; } .location-tree li { padding-left: 1.5rem; position: relative; } .location-tree li:before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 1px; background: var(--border-color); } .location-item { padding: 0.5rem; cursor: pointer; border-radius: 4px; } .location-item:hover { background: rgba(255,255,255,0.1); } .location-item.selected { background: var(--color-info); color: #000; } ``` --- ## ⚙️ FONCTIONNALITÉS PRINCIPALES ### 1. GESTION STOCK - **Quantités**: Totale, Disponible, Seuil d'alerte - **Alertes**: Notification si stock bas - **Mouvements**: Tracking ajouts/retraits - **Emplacement**: Stockage physique ### 2. COMPRESSION D'IMAGES **Configuration** (`image_processing.yaml`): ```yaml compression: enabled: true original: keep: true path: "originals/" optimized: format: "webp" quality: 85 max_width: 1920 max_height: 1080 thumbnail: enabled: true format: "webp" quality: 75 width: 300 height: 300 fit: "cover" max_upload_size_mb: 10 ``` ### 3. RATING SYSTÈME (0-5 ÉTOILES) - Interface clickable - Affichage visuel (★★★★☆) - Stockage float (précision 0.1) - Filtrage par rating ### 4. RECHERCHE AVANCÉE **Critères de recherche**: - Texte libre (nom, marque, modèle) - Type principal / Sous-type - Marque - État (Neuf, Bon, Usagé, Défectueux) - Prix (range) - Date d'achat (range) - Tags - Localisation - En prêt / Disponible - Rating minimum - Vendor ID / Product ID (Linux) **Résultats**: - Temps réel (<200ms) - Pagination - Tri multi-colonnes - Export CSV/JSON ### 5. IMPORT/EXPORT **Export**: - CSV (Excel compatible) - JSON (backup complet) - PDF (fiche périphérique) - Labels (impression étiquettes) **Import**: - CSV en masse - Auto-détection colonnes - Validation données - Preview avant import ### 6. QR CODES - Génération automatique par périphérique - Lien vers fiche web - Impression étiquettes - Scan mobile → accès direct --- ## 📍 SYSTÈME DE LOCALISATION ### Hiérarchie ``` 📍 Racine ├── 🏠 Pièce (piece) │ ├── 🗄️ Placard (placard) │ │ ├── 🗃️ Tiroir (tiroir) │ │ └── 📦 Étagère (etagere) │ └── 🖥️ Meuble (meuble) └── 🏠 Pièce 2 ``` ### Logique **Si périphérique assigné** à un device: - `location_auto = TRUE` → suit le device - Localisation = celle du device **Si périphérique stocké**: - `location_auto = FALSE` - `location_id` → emplacement de stockage précis - `location_details` → précisions (ex: "2ème rangée, sachet antistatique") ### Fonctionnalités - **Arborescence interactive** (TreeView) - **Breadcrumb** (Maison > Bureau > Placard A > Tiroir 2) - **Historique mouvements** (table `peripheral_location_history`) - **QR code par emplacement** (scan → voir contenu) - **Photos emplacements** (aide visuelle) - **Rapport non-localisés** (alerte périphériques sans emplacement) ### Configuration YAML (`locations.yaml`) ```yaml locations: - nom: "Maison" type: "root" children: - nom: "Bureau" type: "piece" children: - nom: "Bureau principal" type: "meuble" - nom: "Placard A" type: "placard" children: - nom: "Tiroir 1" type: "tiroir" description: "Câbles et adaptateurs" - nom: "Tiroir 2" type: "tiroir" description: "Périphériques USB" ``` --- ## 🤝 SYSTÈME DE PRÊT ### Workflow 1. **Créer prêt** - Emprunteur (nom, email, tél) - Dates (prêt, retour prévu) - Caution (montant, type) - État départ + photos - Raison du prêt 2. **Suivi automatique** - Rappel 2j avant échéance - Rappel à l'échéance - Rappel retard (tous les 2j) - Statut: `en_cours`, `en_retard` 3. **Retour** - Date retour effectif - État au retour + photos - Problèmes constatés - Caution rendue/retenue ### Notifications (CRON) **Tâche quotidienne** (`check_and_send_reminders()`): - Vérifier prêts à échéance J-2 - Vérifier prêts à échéance J - Vérifier prêts en retard - Envoyer emails automatiques **Configuration** (`notifications.yaml`): ```yaml loan_notifications: enabled: true rappels: avant_echeance: enabled: true jours_avant: 2 retard: enabled: true frequence_jours: 2 max_rappels: 3 email: enabled: true smtp_server: "smtp.gmail.com" from_email: "inventory@example.com" templates: rappel_avant: sujet: "Rappel: Retour de {peripheral_name} prévu le {date_retour}" corps: | Bonjour {emprunteur_name}, Ce message pour vous rappeler que le périphérique suivant doit être retourné le {date_retour} : • {peripheral_name} • Caution: {caution_montant}€ Merci. ``` ### Statistiques - Total prêts en cours - Valeur totale prêtée - Prêts en retard - Cautions en attente - Top emprunteurs - Score fiabilité emprunteur - Blacklist (optionnel) --- ## 💻 APPAREILS COMPLETS ### Concept Les **appareils complets** (Desktop, Laptop, Tablet, Smartphone, Serveur, Console) sont des périphériques spéciaux qui: 1. **Peuvent avoir des benchmarks** (`linked_device_id` → `devices.id`) 2. **Peuvent avoir des périphériques attachés** (souris, clavier, écran) 3. **Ont des specs complètes** (CPU, RAM, GPU, etc.) 4. **Peuvent être prêtés** comme tout périphérique ### Lien avec Benchmarks ``` Peripheral (Laptop Dell XPS 15) ├─ is_complete_device = TRUE ├─ device_type = "laptop" ├─ linked_device_id = 42 → devices.id (dans data.db) │ └─ → Accès aux benchmarks du device 42 ├─ Historique benchmarks ├─ Dernier score global ├─ Évolution performances └─ Hardware snapshots ``` ### Interface **Onglet "Benchmarks"** dans fiche périphérique: - Dernier benchmark (score global, détails) - Historique (tableau, graphique évolution) - Lien vers page device detail - Bouton "Nouveau benchmark" **Sync automatique specs**: - Après chaque benchmark - Mise à jour auto des specs (CPU, RAM, GPU, etc.) - Depuis `hardware_snapshot` ### Périphériques Attachés **Exemple Desktop Gaming**: ``` Desktop Gaming PC ├─ Linked device → Benchmarks ├─ Périphériques attachés: │ ├─ Souris Logitech G Pro │ ├─ Clavier Corsair K95 │ ├─ Casque SteelSeries │ └─ Écran ASUS ROG 27" └─ Localisation: Bureau > Bureau gaming ``` **Gestion**: - Bouton "Attacher périphérique" - Liste avec possibilité détacher - Tracking historique --- ## 📝 CONFIGURATION YAML ### Fichiers de Configuration ``` /backend/config/ ├── peripheral_types.yaml # Types & catégories ├── locations.yaml # Emplacements ├── image_processing.yaml # Compression images └── notifications.yaml # Alertes & emails ``` ### peripheral_types.yaml Structure extensible pour définir les types de périphériques: ```yaml categories: - id: connectivity nom: "Connectivité" icon: "plug" types: - id: usb_storage nom: "Stockage USB" icon: "usb-drive" champs_specifiques: - capacite_go: type: "number" required: true label: "Capacité (Go)" - usb_version: type: "select" options: ["2.0", "3.0", "3.1", "3.2"] label: "Version USB" - vitesse_lecture_mbs: type: "number" label: "Vitesse lecture (MB/s)" - id: cables nom: "Câbles" icon: "cable" types: - id: cable_hdmi nom: "Câble HDMI" champs_specifiques: - longueur_m: type: "number" required: true label: "Longueur (m)" - version_hdmi: type: "select" options: ["1.4", "2.0", "2.1"] - certification: type: "select" options: ["Standard", "Premium", "Ultra"] - id: complete_devices nom: "Appareils Complets" icon: "devices" is_special: true types: - id: desktop nom: "PC de Bureau (Desktop)" is_complete_device: true can_have_benchmarks: true champs_specifiques: - systeme_exploitation: type: "select" options: ["Windows 11", "Windows 10", "Linux", "macOS"] - cpu_modele: type: "text" placeholder: "ex: Core i7-12700K" # ... (voir section Types de Périphériques pour specs complètes) - id: console_gaming nom: "Console de Jeu" icon: "gamepad" is_complete_device: true champs_specifiques: - type_console: type: "select" options: ["Salon", "Portable", "Hybrid"] - marque: type: "select" options: ["PlayStation", "Xbox", "Nintendo", "Steam", "Autre"] # ... (voir section Types de Périphériques pour specs complètes) # Types personnalisés ajoutés dynamiquement depuis l'interface custom_types: [] ``` ### Gestion Dynamique des Types **Interface admin** (`TypeManager.js`): - Modal gestion types - Ajout/édition/suppression catégories - Ajout/édition/suppression types - Configuration champs spécifiques - Sauvegarde YAML automatique **Avantages**: - ✅ Pas besoin modifier code pour nouveau type - ✅ Interface [+] dans formulaires - ✅ Persistance YAML - ✅ Sync base de données automatique --- ## 📐 PLAN DE DÉPLOIEMENT ### PHASE 1: Backend - Base de Données et API (Semaine 1-2) #### 1.1 Configuration et Sessions - [x] Mettre à jour `/backend/app/core/config.py` - Ajouter `PERIPHERALS_DB_URL` - Ajouter paramètres compression images - Ajouter `PERIPHERALS_MODULE_ENABLED` - [x] Créer `/backend/app/db/session.py` (deux sessions) - `get_db()` → DB principale - `get_peripherals_db()` → DB périphériques - [x] Créer `BasePeripherals` dans `/backend/app/db/base.py` #### 1.2 Modèles SQLAlchemy - [x] Créer `/backend/app/models/peripheral.py` - `Peripheral` (table principale) - `PeripheralPhoto` - `PeripheralDocument` - `PeripheralLink` - `PeripheralLoan` - [x] Créer `/backend/app/models/location.py` - `Location` (hiérarchie emplacements) - [x] Créer `/backend/app/models/peripheral_history.py` - `PeripheralLocationHistory` #### 1.3 Schémas Pydantic - [x] Créer `/backend/app/schemas/peripheral.py` - `PeripheralBase`, `PeripheralCreate`, `PeripheralUpdate` - `PeripheralDetail`, `PeripheralSummary`, `PeripheralListResponse` - [x] Créer `/backend/app/schemas/location.py` - [x] Créer `/backend/app/schemas/loan.py` #### 1.4 Services - [x] Créer `/backend/app/services/peripheral_service.py` - `get_peripheral_with_device()` (cross-DB) - `link_peripheral_to_device()` - `compress_image()`, `generate_thumbnail()` - `sync_peripheral_from_benchmark()` - [x] Créer `/backend/app/services/location_service.py` - `get_location_tree()` (hiérarchie) - `get_full_location_path()` - [x] Créer `/backend/app/services/loan_service.py` - `check_and_send_reminders()` (CRON) - `send_loan_reminder()` #### 1.5 Routes API - [x] Créer `/backend/app/api/peripherals.py` (20+ endpoints) - CRUD périphériques - Photos, Documents, Liens - Prêts, Localisation, Historique - Recherche avancée - Appareils complets (link device, benchmarks) - [x] Créer `/backend/app/api/locations.py` - CRUD emplacements - Arborescence - QR codes - [x] Créer `/backend/app/api/loans.py` - CRUD prêts - Retours - Rappels - Statistiques #### 1.6 Configuration YAML - [x] Créer `/backend/config/peripheral_types.yaml` - Définir toutes les catégories - Définir tous les types (USB, Câbles, Desktop, Console, etc.) - Champs spécifiques par type - [x] Créer `/backend/config/locations.yaml` - Structure initiale emplacements - [x] Créer `/backend/config/image_processing.yaml` - Paramètres compression - [x] Créer `/backend/config/notifications.yaml` - Templates emails - Configuration SMTP #### 1.7 Utilitaires - [x] Créer `/backend/app/utils/yaml_loader.py` - Charger/sauvegarder YAML - Parser configurations - [x] Créer `/backend/app/utils/image_processor.py` - Compression images (Pillow) - Génération thumbnails - Validation formats - [x] Créer `/backend/app/utils/qr_generator.py` - Génération QR codes - [x] Créer `/backend/app/tasks/daily_loan_reminders.py` - Tâche CRON quotidienne #### 1.8 Initialisation - [x] Mettre à jour `/backend/app/db/init_db.py` - Créer tables `peripherals.db` - Charger YAML initial - Créer emplacements par défaut - [x] Mettre à jour `/backend/app/main.py` - Include routers peripherals - Check `PERIPHERALS_MODULE_ENABLED` --- ### PHASE 2: Frontend - Interface Utilisateur (Semaine 3-4) #### 2.1 Pages HTML - [x] Créer `/frontend/peripherals.html` - Layout deux panneaux - Liste + Détails/Formulaire - Navigation onglets - [x] Créer `/frontend/peripheral_detail.html` (optionnel) - Vue complète périphérique - Onglets: Infos, Photos, Docs, Liens, Historique, Benchmarks - [x] Créer `/frontend/locations.html` - Gestion emplacements - Arborescence interactive - [x] Créer `/frontend/loans.html` - Liste prêts en cours - Historique - Statistiques #### 2.2 JavaScript - API Client - [x] Étendre `/frontend/js/api.js` - `BenchAPI.Peripherals.*` (CRUD, photos, docs, liens, prêts) - `BenchAPI.Locations.*` - `BenchAPI.Loans.*` #### 2.3 JavaScript - Logique Pages - [x] Créer `/frontend/js/peripherals.js` - Initialisation page - Chargement liste - Recherche/filtres temps réel - Formulaire dynamique (selon type) - Upload médias - [x] Créer `/frontend/js/peripheral_detail.js` - Gestion onglets - Affichage benchmarks - Galerie photos - [x] Créer `/frontend/js/locations.js` - TreeView emplacements - CRUD emplacements - QR codes - [x] Créer `/frontend/js/loans.js` - Formulaire prêt - Formulaire retour - Liste prêts - Rappels #### 2.4 Composants Réutilisables - [x] Créer `/frontend/js/components/PhotoGallery.js` - Grid photos - Upload drag & drop - Lightbox - Compression option - [x] Créer `/frontend/js/components/DocumentList.js` - Liste documents avec icônes - Upload - Preview PDF - [x] Créer `/frontend/js/components/PeripheralFilters.js` - Filtres latéraux - Recherche avancée - Tags - [x] Créer `/frontend/js/components/LocationSelector.js` - TreeView sélection - Breadcrumb - [x] Créer `/frontend/js/components/RatingStars.js` - Système étoiles clickable - Affichage/édition - [x] Créer `/frontend/js/components/TypeManager.js` - Modal gestion types YAML - CRUD types/catégories - [x] Créer `/frontend/js/components/StockManager.js` - Gestion quantités - Alertes stock - [x] Créer `/frontend/js/components/ImageUploader.js` - Upload multiple - Preview - Crop/rotate basique #### 2.5 CSS - [x] Étendre `/frontend/css/components.css` - Styles périphériques - Cards - Badges (type, état, prêt) - Photo gallery - Rating stars - Location tree - Modals - Forms dynamiques #### 2.6 Navigation - [x] Mettre à jour TOUTES les pages HTML - Ajouter lien "Périphériques" dans nav - Ordre: Dashboard | Devices | **Périphériques** | Prêts | Emplacements | Settings --- ### PHASE 3: Fonctionnalités Avancées (Semaine 5) #### 3.1 Recherche Avancée - [x] Interface recherche multi-critères - [x] Résultats temps réel - [x] Filtres combinables - [x] Tri multi-colonnes #### 3.2 Import/Export - [x] Export CSV/JSON - [x] Import CSV en masse - [x] Export PDF fiche périphérique - [x] Impression étiquettes #### 3.3 QR Codes - [x] Génération QR par périphérique - [x] Génération QR par emplacement - [x] Page scan → fiche - [x] Impression batch #### 3.4 Statistiques - [x] Dashboard périphériques - Total par type (Pie chart) - Valeur inventaire - Top marques (Bar chart) - État (Donut chart) - [x] Dashboard prêts - Prêts en cours - Valeur prêtée - Alertes retards - [x] Dashboard emplacements - Inventaire par emplacement - Valeur par pièce --- ### PHASE 4: Intégration et Optimisation (Semaine 6) #### 4.1 Intégration Devices - [x] Dans `device_detail.html`, ajouter onglet "Périphériques connectés" - [x] Bouton "Créer fiche périphérique" depuis device - [x] Auto-link lors création - [x] Sync specs après benchmark #### 4.2 Détection Automatique - [x] Parser `lsusb` output - [x] Parser `lspci` output - [x] Suggérer ajout auto nouveaux périphériques - [x] Pré-remplir vendor/product ID #### 4.3 Notifications - [x] Setup CRON pour rappels prêts - [x] Templates emails - [x] Configuration SMTP - [x] Alertes stock bas - [x] Alertes garantie expirée #### 4.4 Performance - [x] Optimisation queries SQL (eager loading) - [x] Index appropriés - [x] Compression images - [x] Cache recherches fréquentes - [x] Pagination efficace #### 4.5 Sécurité - [x] Validation uploads (MIME, taille) - [x] Sanitization noms fichiers - [x] Protection CSRF - [x] Rate limiting API --- ### PHASE 5: Documentation et Tests (Semaine 7) #### 5.1 Documentation - [x] `/docs/PERIPHERALS_USER_GUIDE.md` - Guide utilisateur - Exemples d'utilisation - FAQ - [x] `/docs/PERIPHERALS_API.md` - Documentation API (Swagger) - Exemples requêtes - [x] `/docs/PERIPHERALS_ADMIN.md` - Configuration YAML - Gestion types - Maintenance #### 5.2 Exemples et Fixtures - [x] Script seed données test - [x] Templates JSON par type - [x] Données d'exemple #### 5.3 Tests - [x] Tests unitaires backend (pytest) - Models - Services - Utils - [x] Tests intégration API - Endpoints CRUD - Cross-DB queries - [x] Tests frontend - Validation formulaires - Composants --- ## 🚀 PROMPT DE DÉVELOPPEMENT ### Contexte du Projet Vous êtes chargé d'implémenter le **Module Périphériques** pour l'application **Linux BenchTools** existante. Cette application permet actuellement de benchmarker des machines Linux et de stocker leurs résultats. ### Architecture Existante **Backend**: - FastAPI + SQLAlchemy - Base de données: SQLite `data.db` - Structure: `/backend/app/` avec models/, schemas/, api/, core/ - Authentification par token **Frontend**: - Vanilla JavaScript (pas de framework) - Thème: Monokai dark avec CSS variables - Layout: Two-panel design - Navigation: Dashboard, Devices, Settings ### Objectif du Module Créer un **système complet de gestion d'inventaire de périphériques** avec: 1. **Catalogue exhaustif** de périphériques (USB, Bluetooth, Câbles, Visserie, Appareils complets, Consoles) 2. **Localisation physique** (Pièces → Placards → Tiroirs) 3. **Gestion de prêts** (avec rappels automatiques) 4. **Intégration benchmarks** (pour Desktop, Laptop, Serveur) 5. **Fonctionnalités avancées** (Stock, Rating, Photos, Documents, QR codes) ### Décisions Architecturales Clés #### 1. Base de Données Séparée **IMPORTANT**: Créer une **nouvelle base de données** `peripherals.db` séparée de `data.db`. **Pourquoi**: - Isolation des données - Performance (pas de contention) - Backups indépendants - Module activable/désactivable **Lien entre DB**: - `peripherals.linked_device_id` → référence logique vers `devices.id` (pas FK SQL) - Gérer relations manuellement côté application via service layer #### 2. Configuration YAML Dynamique Les **types de périphériques** sont définis en YAML, modifiables depuis l'interface: ```yaml # /backend/config/peripheral_types.yaml categories: - id: cables nom: "Câbles" types: - id: cable_hdmi nom: "Câble HDMI" champs_specifiques: - longueur_m: { type: "number", required: true } - version_hdmi: { type: "select", options: ["1.4", "2.0", "2.1"] } ``` **Avantages**: - Pas de modification code pour nouveau type - Interface admin [+] pour ajouter types - Persistance automatique #### 3. Appareils Complets avec Benchmarks Les **Desktop, Laptop, Serveur, Console** sont des périphériques spéciaux: - `is_complete_device = TRUE` - `linked_device_id` → lien vers `devices.id` pour benchmarks - Affichage historique benchmarks intégré - Sync automatique specs depuis `hardware_snapshot` #### 4. Système de Localisation Hiérarchique Arborescence emplacements: ``` Maison ├─ Bureau (piece) │ ├─ Placard A (placard) │ │ ├─ Tiroir 1 (tiroir) │ │ └─ Tiroir 2 (tiroir) │ └─ Bureau principal (meuble) └─ Salon (piece) ``` **Logique**: - Si `device_id != NULL` et `location_auto = TRUE` → localisation = celle du device - Sinon → `location_id` (emplacement de stockage) #### 5. Compression d'Images Automatique Configuration dans `image_processing.yaml`: - Format optimisé: WebP - Qualité: 85% - Thumbnails: 300x300px - Conserver original optionnel ### Structure des Données #### Table Principale: `peripherals` **Champs globaux** (tous périphériques): - Identification (nom, type, marque, modèle, S/N) - Achat (boutique, date, prix, garantie) - Évaluation (rating 0-5 étoiles) - Stock (quantité totale, disponible, seuil alerte) - Métadonnées (dates, état, tags, notes) - Linux (device_path, vendor_id, product_id, driver) - Installation (auto, driver requis, problèmes connus) - Connectivité (interface, consommation) - Localisation (location_id, location_auto) - Prêt (en_pret, pret_actuel_id, prete_a) - Appareil complet (is_complete_device, linked_device_id) - **Données spécifiques** (JSON flexible par type) **Relations**: - `peripheral_photos` (1:N, photos avec principale) - `peripheral_documents` (1:N, manuels/factures/datasheets) - `peripheral_links` (1:N, liens fabricant/support/drivers) - `peripheral_loans` (1:N, historique prêts) - `peripheral_location_history` (1:N, mouvements) - `locations` (hiérarchie N niveaux) ### Implémentation Backend #### Sessions DB ```python # /backend/app/db/session.py engine_main = create_engine(settings.DATABASE_URL) # data.db engine_peripherals = create_engine(settings.PERIPHERALS_DB_URL) # peripherals.db def get_db() -> Session: """Session DB principale (benchmarks)""" ... def get_peripherals_db() -> Session: """Session DB périphériques""" ... ``` #### Modèles ```python # /backend/app/models/peripheral.py from app.db.base import BasePeripherals # ← Pas Base ! class Peripheral(BasePeripherals): __tablename__ = "peripherals" # ... champs # PAS de relationship SQLAlchemy vers data.db # Gérer manuellement via service layer ``` #### Service Layer (Cross-DB) ```python # /backend/app/services/peripheral_service.py class PeripheralService: @staticmethod def get_peripheral_with_device( peripheral_id: int, db_peripherals: Session, db_main: Session ): """Récupère périphérique + device lié (cross-DB)""" peripheral = db_peripherals.query(Peripheral).get(peripheral_id) if peripheral.linked_device_id: device = db_main.query(Device).get(peripheral.linked_device_id) benchmarks = device.benchmarks if device else [] return { "peripheral": peripheral, "linked_device": device, "benchmarks": benchmarks } return {"peripheral": peripheral} ``` #### Routes API (Deux Sessions) ```python # /backend/app/api/peripherals.py @router.get("/{peripheral_id}") async def get_peripheral( peripheral_id: int, db_peripherals: Session = Depends(get_peripherals_db), # ← DB périphériques db_main: Session = Depends(get_db) # ← DB principale ): """Détail avec benchmarks si appareil complet""" return PeripheralService.get_peripheral_with_device( peripheral_id, db_peripherals, db_main ) ``` ### Implémentation Frontend #### Structure ``` /frontend/ ├── peripherals.html # Page principale (two-panel) ├── peripheral_detail.html # Détail (optionnel) ├── locations.html # Gestion emplacements ├── loans.html # Gestion prêts ├── js/ │ ├── api.js # Extension BenchAPI.Peripherals.* │ ├── peripherals.js # Logique page │ ├── components/ │ │ ├── PhotoGallery.js # Galerie + upload │ │ ├── RatingStars.js # Système étoiles │ │ ├── LocationSelector.js # TreeView │ │ └── TypeManager.js # Gestion types YAML │ └── ... └── css/ └── components.css # Styles périphériques ``` #### API Client Extension ```javascript // /frontend/js/api.js window.BenchAPI.Peripherals = { getPeripherals: async (page, filters) => { /* ... */ }, getPeripheral: async (id) => { /* ... */ }, createPeripheral: async (data) => { /* ... */ }, uploadPhoto: async (peripheralId, file, compress = true) => { /* ... */ }, createLoan: async (peripheralId, loanData) => { /* ... */ }, linkDevice: async (peripheralId, deviceId) => { /* ... */ }, getBenchmarks: async (peripheralId) => { /* ... */ }, // ... 20+ méthodes }; ``` #### Composants Réutilisables **PhotoGallery**: ```javascript class PhotoGallery { constructor(containerId, peripheralId) { /* ... */ } async load() { /* Charger photos */ } render() { /* Grid + lightbox */ } async upload(files) { /* Upload avec compression */ } } ``` **RatingStars**: ```javascript class RatingStars { constructor(containerId, currentRating = 0, editable = true) { /* ... */ } render() { /* ★★★★☆ */ } setRating(value) { /* Clickable */ } } ``` **LocationSelector**: ```javascript class LocationSelector { constructor(containerId) { /* ... */ } async loadTree() { /* Arborescence depuis API */ } render() { /* TreeView avec expand/collapse */ } getSelectedPath() { /* Breadcrumb */ } } ``` ### Fonctionnalités Clés à Implémenter #### 1. Gestion de Prêt avec Notifications **Backend**: - CRON quotidien (`check_and_send_reminders()`) - Rappels 2j avant, à échéance, en retard - Templates emails configurables (YAML) **Frontend**: - Modal prêt (emprunteur, dates, caution, photos) - Modal retour (état, problèmes, caution rendue) - Liste prêts actifs - Alertes retards #### 2. Compression d'Images ```python # /backend/app/utils/image_processor.py def compress_image(file_path: str, quality: int = 85): from PIL import Image img = Image.open(file_path) # Resize si trop grand if img.width > settings.IMAGE_MAX_WIDTH: ratio = settings.IMAGE_MAX_WIDTH / img.width new_size = (int(img.width * ratio), int(img.height * ratio)) img = img.resize(new_size, Image.LANCZOS) # Convertir en WebP output_path = file_path.replace(ext, '.webp') img.save(output_path, 'WEBP', quality=quality) return output_path def generate_thumbnail(file_path: str, size: int = 300): # Crop/resize 300x300 ... ``` #### 3. QR Codes ```python # /backend/app/utils/qr_generator.py import qrcode def generate_peripheral_qr(peripheral_id: int): url = f"{settings.FRONTEND_URL}/peripherals/{peripheral_id}" img = qrcode.make(url) path = f"/uploads/peripherals/qr_{peripheral_id}.png" img.save(path) return path ``` #### 4. Détection Auto Linux ```python # /backend/app/services/detection_service.py def parse_lsusb_output(output: str) -> List[Dict]: """Parse lsusb et suggère périphériques""" devices = [] for line in output.split('\n'): # Parse: Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver if match := re.match(r'Bus (\d+) Device (\d+): ID ([0-9a-f]{4}):([0-9a-f]{4}) (.+)', line): devices.append({ 'vendor_id': match.group(3), 'product_id': match.group(4), 'description': match.group(5), 'suggested_name': parse_device_name(match.group(5)) }) return devices ``` ### Considérations Importantes #### Thème et UX - **Cohérence visuelle**: Utiliser le même thème Monokai que l'existant - **Two-panel layout**: Liste gauche (20%) + Détails/Formulaire droite (80%) - **Icons**: Icônes SVG par type de périphérique - **Badges**: Colorés selon état (Neuf=vert, Usagé=orange, Défectueux=rouge) - **Responsive**: Grid layout adaptatif #### Performance - **Pagination**: 50 items par page - **Lazy loading**: Images chargées à la demande - **Cache**: Recherches fréquentes (types, marques) - **Index DB**: Sur tous les champs de recherche #### Sécurité - **Validation uploads**: MIME type, taille max (10MB) - **Sanitization**: Noms fichiers (supprimer caractères spéciaux) - **CORS**: Restreindre origines - **Rate limiting**: 100 req/min par IP #### Extensibilité - **YAML dynamique**: Nouveaux types sans modifier code - **JSON flexible**: `caracteristiques_specifiques` adapté à chaque type - **Plugins**: Architecture permettant ajout de modules (future) ### Checklist d'Implémentation #### Backend ✓ - [x] Deux bases de données (sessions séparées) - [x] Modèles SQLAlchemy (BasePeripherals) - [x] Schémas Pydantic (CRUD + relations) - [x] Service layer (cross-DB queries) - [x] Routes API (20+ endpoints) - [x] Configuration YAML (types, locations, images, notifications) - [x] Compression images (Pillow) - [x] QR codes (qrcode lib) - [x] CRON notifications (rappels prêts) - [x] Init DB (création tables + seed data) #### Frontend ✓ - [x] Pages HTML (peripherals, locations, loans) - [x] Extension API client (BenchAPI.Peripherals.*) - [x] Logique pages (peripherals.js, locations.js, loans.js) - [x] Composants réutilisables (PhotoGallery, RatingStars, LocationSelector, TypeManager) - [x] CSS cohérent (thème Monokai) - [x] Navigation mise à jour (toutes les pages) #### Fonctionnalités ✓ - [x] CRUD périphériques - [x] Upload photos/documents - [x] Gestion prêts (création, retour, rappels) - [x] Localisation (arborescence, historique) - [x] Appareils complets (lien benchmarks) - [x] Recherche avancée - [x] Stock management - [x] Rating 0-5 étoiles - [x] QR codes - [x] Import/Export CSV/JSON #### Tests & Documentation ✓ - [x] Tests unitaires backend - [x] Tests intégration API - [x] Documentation utilisateur - [x] Documentation API (Swagger) - [x] Exemples et fixtures ### Commandes Utiles ```bash # Créer les bases de données cd /backend python -c "from app.db.init_db import init_db; init_db()" # Lancer backend cd /backend uvicorn app.main:app --reload --host 0.0.0.0 --port 8007 # Tests pytest tests/ # Restart Docker docker-compose restart backend ``` ### Points d'Attention 1. **Ne JAMAIS créer de FK SQL entre data.db et peripherals.db** (gérer manuellement) 2. **Toujours utiliser deux sessions** dans les routes avec cross-DB queries 3. **Valider YAML** lors du chargement (schéma pydantic) 4. **Compresser images par défaut** (économie espace) 5. **Logger toutes les actions** (historique, audit) 6. **Tester avec charge** (1000+ périphériques) ### Résultat Attendu Un **module périphériques complet, professionnel et extensible** qui: ✅ Permet d'inventorier TOUT le matériel informatique ✅ Localise précisément chaque élément ✅ Gère les prêts avec rappels automatiques ✅ S'intègre aux benchmarks existants ✅ Offre une recherche rapide et puissante ✅ Est facile à étendre (types YAML) ✅ Respecte le design existant (Monokai, two-panel) ✅ Fonctionne en production (sécurisé, performant) **Bonne implémentation !** 🚀