import os import json import logging import pandas as pd import xmlrpc.client from typing import Dict, List, Any from dotenv import load_dotenv load_dotenv() # Configuration logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Configuration Odoo simplifiée ODOO_URL = os.getenv("ODOO_URL", "") ODOO_DB = os.getenv("ODOO_DB", "") ODOO_USERNAME = os.getenv("ODOO_USERNAME", "") ODOO_PASSWORD = os.getenv("ODOO_PASSWORD", "") # Configuration optionnelle ODOO_TIMEOUT = int(os.getenv("ODOO_TIMEOUT", "30")) ODOO_VERIFY_SSL = os.getenv("ODOO_VERIFY_SSL", "true").lower() == "true" # Configuration MCP pour Gradio GRADIO_MCP_SERVER = os.getenv("GRADIO_MCP_SERVER", "True") # ============================================================================= # CLIENT ODOO ET GESTION DE CONNEXION # ============================================================================= class SimpleOdooClient: """Client Odoo simplifié pour les opérations de base""" def __init__(self): self.url = None self.db = None self.username = None self.password = None self.uid = None self.models = None def connect(self, url: str, db: str, username: str, password: str) -> Dict[str, Any]: """Connexion à Odoo""" try: # Connexion d'authentification common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common') # Test de version (connexion) version = common.version() # Authentification uid = common.authenticate(db, username, password, {}) if not uid: return {"success": False, "error": "Échec d'authentification"} # Connexion aux modèles models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object') # Stockage des informations de connexion self.url = url self.db = db self.username = username self.password = password self.uid = uid self.models = models return { "success": True, "message": f"Connecté en tant que {username}", "version": version.get('server_version', 'Inconnue') } except Exception as e: return {"success": False, "error": str(e)} def is_connected(self) -> bool: """Vérifie si connecté""" return self.uid is not None def search_read(self, model: str, domain: List = None, fields: List = None, limit: int = 10) -> List[Dict]: """Recherche et lecture d'enregistrements""" if not self.is_connected(): raise Exception("Non connecté à Odoo") domain = domain or [] fields = fields or [] return self.models.execute_kw( self.db, self.uid, self.password, model, 'search_read', [domain], {'fields': fields, 'limit': limit} ) def search_count(self, model: str, domain: List = None) -> int: """Compte les enregistrements""" if not self.is_connected(): raise Exception("Non connecté à Odoo") domain = domain or [] return self.models.execute_kw( self.db, self.uid, self.password, model, 'search_count', [domain] ) def read_group(self, model: str, domain: List = None, fields: List = None, groupby: List = None) -> List[Dict]: """Groupe et agrège les données""" if not self.is_connected(): raise Exception("Non connecté à Odoo") domain = domain or [] fields = fields or [] groupby = groupby or [] return self.models.execute_kw( self.db, self.uid, self.password, model, 'read_group', [domain, fields, groupby] ) def search(self, model: str, domain: List = None, limit: int = None) -> List[int]: """Recherche des IDs d'enregistrements""" if not self.is_connected(): raise Exception("Non connecté à Odoo") domain = domain or [] options = {} if limit: options['limit'] = limit return self.models.execute_kw( self.db, self.uid, self.password, model, 'search', [domain], options ) def create(self, model: str, values: Dict) -> int: """Crée un enregistrement""" if not self.is_connected(): raise Exception("Non connecté à Odoo") return self.models.execute_kw( self.db, self.uid, self.password, model, 'create', [values] ) def write(self, model: str, record_id: int, values: Dict) -> bool: """Met à jour un enregistrement""" if not self.is_connected(): raise Exception("Non connecté à Odoo") return self.models.execute_kw( self.db, self.uid, self.password, model, 'write', [[record_id], values] ) def unlink(self, model: str, record_id: int) -> bool: """Supprime un enregistrement""" if not self.is_connected(): raise Exception("Non connecté à Odoo") return self.models.execute_kw( self.db, self.uid, self.password, model, 'unlink', [[record_id]] ) # ============================================================================= # PERSISTANCE DES CREDENTIALS # ============================================================================= def save_credentials_to_file(url: str, database: str, username: str, password: str): """Sauvegarde les credentials dans odoo_config.json""" config = { "url": url, "db": database, "username": username, "password": password, "timestamp": pd.Timestamp.now().isoformat() } try: with open('odoo_config.json', 'w') as f: json.dump(config, f, indent=2) return True except Exception as e: logger.error(f"Erreur sauvegarde credentials: {e}") return False def load_credentials_from_file() -> Dict[str, str]: """Charge les credentials depuis odoo_config.json""" try: if os.path.exists('odoo_config.json'): with open('odoo_config.json', 'r') as f: config = json.load(f) return { "url": config.get('url', ''), "database": config.get('db', ''), "username": config.get('username', ''), "password": config.get('password', '') } except Exception as e: logger.error(f"Erreur chargement credentials: {e}") return {"url": "", "database": "", "username": "", "password": ""} # ============================================================================= # CLIENT GLOBAL ET AUTO-CONNEXION # ============================================================================= # Client global - utilisé par GRADIO ET MCP client = SimpleOdooClient() # Variables globales pour stocker les credentials (+ persistance fichier) stored_credentials = load_credentials_from_file() def auto_connect_if_needed() -> bool: """Tente une auto-connexion si nécessaire, retourne True si connecté""" if client.is_connected(): return True # ✅ CHARGEMENT AUTOMATIQUE DEPUIS FICHIER SI NÉCESSAIRE global stored_credentials if not all(stored_credentials.values()): stored_credentials = load_credentials_from_file() # Tentative de connexion si credentials disponibles if all(stored_credentials.values()): result = client.connect( stored_credentials["url"], stored_credentials["database"], stored_credentials["username"], stored_credentials["password"] ) return result["success"] return False