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