Spaces:
Sleeping
Sleeping
| 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 |