firstcommit

This commit is contained in:
Ladebeze66 2025-03-17 19:39:32 +01:00
commit c999ab12a9
18 changed files with 485 additions and 0 deletions

58
README.md Normal file
View File

@ -0,0 +1,58 @@
# Gestionnaire de Tickets Odoo Simplifié
Ce projet est une version simplifiée et optimisée du gestionnaire de tickets Odoo. Il permet d'interagir avec une instance Odoo pour gérer des tickets de projet.
## Fonctionnalités
1. **Afficher la liste des modèles** - Affiche tous les modèles disponibles dans l'instance Odoo.
2. **Afficher les champs d'un modèle** - Affiche tous les champs d'un modèle donné.
3. **Exporter les informations des champs en JSON** - Exporte la structure des champs d'un modèle en format JSON.
4. **Exporter les tickets d'un project_id par étape** - Exporte tous les tickets d'un project_id, classés par étape (stage_id).
## Structure du projet
- `main.py` - Point d'entrée principal du programme
- `menu_principal.py` - Gestion du menu principal
- `menu_handlers.py` - Gestionnaires d'actions pour chaque option du menu
- `ticket_manager.py` - Classe principale pour la gestion des tickets et modèles
- `odoo_connection.py` - Gestion de la connexion à l'instance Odoo
- `data_filter.py` - Fonctions pour filtrer et nettoyer les données des tickets
- `utils.py` - Fonctions utilitaires diverses
- `config.py` - Configuration de l'application (connexion Odoo, chemins d'export, etc.)
## Prérequis
- Python 3.6 ou supérieur
- Package `odoorpc` pour la connexion à Odoo
- Package `bs4` (BeautifulSoup) pour le nettoyage des données HTML
## Installation
1. Installer les dépendances :
```
pip install odoorpc bs4
```
2. Configurer les variables d'environnement (ou modifier `config.py`) :
- `ODOO_HOST` : Hôte de l'instance Odoo
- `ODOO_DB` : Nom de la base de données Odoo
- `ODOO_USER` : Nom d'utilisateur Odoo
- `ODOO_PASSWORD` : Mot de passe Odoo
## Utilisation
1. Exécuter le programme :
```
python main.py
```
2. Suivre les instructions du menu pour utiliser les différentes fonctionnalités.
## Exemple d'utilisation
### Exporter les tickets d'un projet par étape
1. Sélectionner l'option 4 dans le menu
2. Entrer l'ID du projet (par exemple, "5")
3. Confirmer l'action
4. Les tickets seront exportés dans le répertoire `exported_tickets/project_5_NomDuProjet/`, classés par étape

View File

13
config.py Normal file
View File

@ -0,0 +1,13 @@
import os
# Configuration Odoo
ODOO_HOST = os.getenv('ODOO_HOST', 'odoo.cbao.fr')
ODOO_DB = os.getenv('ODOO_DB', 'production_cbao')
ODOO_USER = os.getenv('ODOO_USER', 'fernand@cbao.fr')
ODOO_PASSWORD = os.getenv('ODOO_PASSWORD', 'Lestat66!')
# Configuration export
EXPORT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "exported_tickets")
# Créer le répertoire d'export s'il n'existe pas
os.makedirs(EXPORT_DIR, exist_ok=True)

View File

39
data_filter.py Normal file
View File

@ -0,0 +1,39 @@
import re
import html
from bs4 import BeautifulSoup
def clean_html(content):
"""Nettoie le contenu HTML en supprimant les balises."""
if not content:
return ""
soup = BeautifulSoup(content, 'html.parser')
return soup.get_text(separator='\n', strip=True)
def filter_ticket_data(ticket_data):
"""Filtre les données d'un ticket pour ne garder que les informations essentielles."""
# Créer un nouveau dictionnaire pour le ticket filtré
filtered_ticket = {
"ID du Ticket": ticket_data["ID du Ticket"],
"Nom": ticket_data["Nom"],
"Code": ticket_data.get("Code", "N/A"),
"Date Limite": ticket_data["Date Limite"],
"Champs Simples": ticket_data["Champs Simples"], # Conserver tous les champs simples
"Champs Relationnels": ticket_data["Champs Relationnels"], # Conserver tous les champs relationnels
"Discussions": [] # Initialiser la liste des discussions
}
# Nettoyer le champ description dans Champs Simples
if "description" in filtered_ticket["Champs Simples"]:
filtered_ticket["Champs Simples"]["description"] = clean_html(filtered_ticket["Champs Simples"]["description"])
# Garder uniquement les discussions nécessaires
for msg in ticket_data["Discussions"]:
filtered_ticket["Discussions"].append({
"ID Message": msg["ID Message"],
"Sujet": msg["Sujet"],
"Contenu": clean_html(msg["Contenu"]), # Nettoyage du contenu HTML
"Auteur": msg["Auteur"],
"Date": msg["Date"]
})
return filtered_ticket

View File

8
main.py Normal file
View File

@ -0,0 +1,8 @@
from menu_principal import run_menu
def main():
run_menu()
if __name__ == "__main__":
main()

0
main.py:Zone.Identifier Normal file
View File

129
menu_handlers.py Normal file
View File

@ -0,0 +1,129 @@
from ticket_manager import TicketManager
# Initialisation de l'objet
ticket_manager = TicketManager()
def handle_list_models():
"""Gère l'affichage de la liste des modèles"""
ticket_manager.list_models()
def handle_list_model_fields():
"""Gère l'affichage des champs d'un modèle"""
model_name = input("\nEntrez le nom du modèle: ")
if not model_name:
print("Aucun nom de modèle fourni.")
return
ticket_manager.list_model_fields(model_name)
def handle_export_model_fields_to_json():
"""Gère l'exportation des informations des champs d'un modèle en JSON"""
model_name = input("\nEntrez le nom du modèle: ")
if not model_name:
print("Aucun nom de modèle fourni.")
return
filename = input("Entrez le nom du fichier pour l'exportation: ")
if not filename:
print("Aucun nom de fichier fourni.")
return
ticket_manager.export_model_fields_to_json(model_name, filename)
def handle_project_tickets_by_stage():
"""Gère l'exportation des tickets d'un projet par étape"""
# Récupérer la liste des projets disponibles
projects = ticket_manager.get_available_projects()
if not projects:
print("Aucun projet disponible. Impossible de continuer.")
return
# Demander à l'utilisateur de choisir un projet
project_id_input = input("\nEntrez l'ID du projet (ou 'q' pour quitter): ")
if project_id_input.lower() == 'q':
return
try:
project_id = int(project_id_input)
if project_id not in projects:
print(f"Aucun projet trouvé avec l'ID: {project_id}")
return
except ValueError:
print("L'ID du projet doit être un nombre entier.")
return
# Récupérer les étapes (stage_id) du projet
print(f"\nRécupération des étapes du projet: {projects[project_id]} (ID: {project_id})")
stages = ticket_manager.get_project_stages(project_id)
if not stages:
print("Aucune étape trouvée pour ce projet. Impossible de continuer.")
return
# Afficher les étapes disponibles
print("\nÉtapes disponibles:")
for stage_id, stage_name in stages.items():
print(f"ID: {stage_id} - {stage_name}")
# Demander à l'utilisateur s'il veut sélectionner ou exclure des étapes
selection_mode = input("\nSouhaitez-vous:\n1. Exporter toutes les étapes\n2. Sélectionner des étapes spécifiques par ID\n3. Exclure certaines étapes par ID\nVotre choix (1/2/3): ")
selected_stage_ids = None
if selection_mode == '2':
# Sélectionner des étapes spécifiques par ID
stage_id_input = input("Entrez les IDs des étapes à inclure (séparés par des virgules): ")
try:
selected_stage_ids = [int(x.strip()) for x in stage_id_input.split(',') if x.strip()]
# Vérifier que les IDs existent dans les étapes disponibles
valid_ids = [stage_id for stage_id in selected_stage_ids if stage_id in stages]
invalid_ids = [stage_id for stage_id in selected_stage_ids if stage_id not in stages]
selected_stage_ids = valid_ids
if invalid_ids:
print(f"Attention: Les IDs suivants ne sont pas valides et seront ignorés: {', '.join(map(str, invalid_ids))}")
if not selected_stage_ids:
print("Aucun ID d'étape valide sélectionné. Exportation annulée.")
return
print(f"Étapes sélectionnées: {', '.join([f'{stages[stage_id]} (ID: {stage_id})' for stage_id in selected_stage_ids])}")
except ValueError:
print("Erreur dans la sélection des étapes. Format attendu: 1,2,3,...")
return
elif selection_mode == '3':
# Exclure certaines étapes par ID
stage_id_input = input("Entrez les IDs des étapes à exclure (séparés par des virgules): ")
try:
excluded_stage_ids = [int(x.strip()) for x in stage_id_input.split(',') if x.strip()]
# Vérifier que les IDs existent dans les étapes disponibles
valid_excluded_ids = [stage_id for stage_id in excluded_stage_ids if stage_id in stages]
invalid_ids = [stage_id for stage_id in excluded_stage_ids if stage_id not in stages]
excluded_stage_ids = valid_excluded_ids
if invalid_ids:
print(f"Attention: Les IDs suivants ne sont pas valides et seront ignorés: {', '.join(map(str, invalid_ids))}")
# Sélectionner toutes les étapes sauf les exclues
selected_stage_ids = [stage_id for stage_id in stages.keys() if stage_id not in excluded_stage_ids]
if not selected_stage_ids:
print("Toutes les étapes ont été exclues. Exportation annulée.")
return
print(f"Étapes sélectionnées: {', '.join([f'{stages[stage_id]} (ID: {stage_id})' for stage_id in selected_stage_ids])}")
except ValueError:
print("Erreur dans la sélection des étapes. Format attendu: 1,2,3,...")
return
# Confirmer l'action
confirmation = input(f"\nVoulez-vous exporter les tickets du projet {projects[project_id]}? (o/n): ")
if confirmation.lower() != 'o':
print("Exportation annulée.")
return
# Exporter les tickets
ticket_manager.export_tickets_by_project_and_stage(project_id, selected_stage_ids)

View File

36
menu_principal.py Normal file
View File

@ -0,0 +1,36 @@
from menu_handlers import (
handle_list_models,
handle_list_model_fields,
handle_export_model_fields_to_json,
handle_project_tickets_by_stage
)
def display_main_menu():
"""Affiche le menu principal de l'application"""
print("\n==== GESTIONNAIRE DE TICKETS ODOO ====")
print("1. Afficher la liste des modèles")
print("2. Afficher les champs d'un modèle")
print("3. Exporter les informations des champs d'un modèle en JSON")
print("4. Exporter les tickets d'un project_id par étape")
print("5. Quitter")
return input("\nChoisissez une option (1-5): ")
def run_menu():
"""Exécute la boucle du menu principal"""
while True:
choice = display_main_menu()
if choice == '1':
handle_list_models()
elif choice == '2':
handle_list_model_fields()
elif choice == '3':
handle_export_model_fields_to_json()
elif choice == '4':
handle_project_tickets_by_stage()
elif choice == '5':
print("Au revoir!")
break
else:
print("Option invalide. Veuillez choisir entre 1 et 5.")

View File

33
odoo_connection.py Normal file
View File

@ -0,0 +1,33 @@
import odoorpc
from config import ODOO_HOST, ODOO_DB, ODOO_USER, ODOO_PASSWORD
class OdooConnection:
"""Gère la connexion à l'instance Odoo"""
def __init__(self):
self.odoo = None
self.connected = False
def connect(self):
"""Établit la connexion à Odoo"""
try:
self.odoo = odoorpc.ODOO(ODOO_HOST, port=443, protocol='jsonrpc+ssl')
print(f"Connexion réussie à {ODOO_HOST}")
self.odoo.login(ODOO_DB, ODOO_USER, ODOO_PASSWORD)
print(f"Authentifié en tant que {ODOO_USER}")
self.connected = True
return True
except odoorpc.error.RPCError as e:
print(f"Erreur RPC Odoo : {e}")
return False
except Exception as e:
print(f"Erreur inattendue : {e}")
return False
def get_odoo_instance(self):
"""Retourne l'instance Odoo connectée"""
if not self.connected:
self.connect()
return self.odoo

View File

131
ticket_manager.py Normal file
View File

@ -0,0 +1,131 @@
from odoo_connection import OdooConnection
import os
import json
from utils import save_json, ensure_export_directory
from config import EXPORT_DIR
from data_filter import filter_ticket_data
class TicketManager:
"""Gestionnaire de tickets simplifié avec seulement les fonctionnalités essentielles"""
def __init__(self):
"""Initialise le gestionnaire de tickets"""
self.conn = OdooConnection()
self.odoo = self.conn.get_odoo_instance()
self.model_name = "project.task"
def _check_connection(self):
"""Vérifie la connexion Odoo"""
if self.odoo is None:
try:
self.conn = OdooConnection()
self.odoo = self.conn.get_odoo_instance()
except Exception as e:
print(f"Erreur de connexion: {e}")
self.odoo = None
return self.odoo is not None
def _safe_execute(self, model, method, *args):
"""Exécute une méthode Odoo de manière sécurisée"""
if not self._check_connection():
return None
try:
return self.odoo.execute(model, method, *args)
except Exception as e:
print(f"Erreur lors de {method} sur {model}: {e}")
return None
def list_models(self):
"""Affiche la liste des modèles disponibles dans Odoo"""
models = self._safe_execute('ir.model', 'search_read', [], ['model', 'name'])
if not models:
print("Aucun modèle disponible.")
return []
print("\nListe des modèles disponibles:")
for model in models:
print(f"Modèle: {model['name']} (ID: {model['model']})")
return models
def list_model_fields(self, model_name):
"""Affiche les champs d'un modèle donné"""
fields_info = self._safe_execute(model_name, 'fields_get')
if not fields_info:
print(f"Aucun champ trouvé pour le modèle {model_name}.")
return []
print(f"\nChamps du modèle {model_name}:")
for field_name, field_data in fields_info.items():
print(f"Champ: {field_name} - Type: {field_data['type']}")
return fields_info
def get_project_tickets_summary(self, project_id):
"""Récupère un résumé des tickets d'un projet pour permettre la sélection"""
domain = [('project_id', '=', project_id)]
ticket_ids = self._safe_execute(self.model_name, 'search', domain, 0, 200)
if not ticket_ids:
print(f"Aucun ticket trouvé pour le projet ID: {project_id}")
return []
fields_to_read = ['id', 'name', 'code', 'stage_id', 'date_deadline']
tickets_data = self._safe_execute(self.model_name, 'read', ticket_ids, fields_to_read)
if not tickets_data:
print("Erreur lors de la récupération des données des tickets.")
return []
summary_tickets = []
for ticket in tickets_data:
stage_name = "Non défini"
if ticket.get('stage_id'):
stage_name = ticket['stage_id'][1] if isinstance(ticket['stage_id'], list) and len(ticket['stage_id']) > 1 else str(ticket['stage_id'])
summary_tickets.append({
'id': ticket['id'],
'name': ticket['name'],
'code': ticket.get('code', 'N/A'),
'stage_id': ticket.get('stage_id', [0, "Non défini"]),
'stage_name': stage_name,
'date_deadline': ticket.get('date_deadline', 'Non défini')
})
return summary_tickets
def export_tickets_by_project_and_stage(self, project_id, selected_stage_ids=None):
"""Exporte les tickets d'un projet classés par étape"""
project_data = self._safe_execute('project.project', 'search_read', [('id', '=', project_id)], ['id', 'name'])
if not project_data:
print(f"Projet ID {project_id} introuvable")
return
project_name = project_data[0]['name']
domain = [('project_id', '=', project_id)]
if selected_stage_ids:
domain.append(('stage_id', 'in', selected_stage_ids))
ticket_ids = self._safe_execute(self.model_name, 'search', domain, 0, 1000)
if not ticket_ids:
print("Aucun ticket trouvé")
return
tickets = [self.get_ticket_by_id(ticket_id) for ticket_id in ticket_ids]
tickets_by_stage = {}
for ticket in tickets:
stage_id = ticket["Champs Relationnels"].get("stage_id", [0, "Non classé"])[0]
stage_name = ticket["Champs Relationnels"].get("stage_id", [0, "Non classé"])[1]
key = f"{stage_id}_{stage_name}"
if key not in tickets_by_stage:
tickets_by_stage[key] = []
tickets_by_stage[key].append(ticket)
project_dir = ensure_export_directory(f"project_{project_id}_{project_name.replace(' ', '_')}")
for stage_key, stage_tickets in tickets_by_stage.items():
stage_dir = os.path.join(project_dir, stage_key)
os.makedirs(stage_dir, exist_ok=True)
save_json(os.path.join(stage_dir, "all_tickets.json"), stage_tickets)
print(f"Exportation terminée dans {project_dir}/")

View File

38
utils.py Normal file
View File

@ -0,0 +1,38 @@
import os
import json
from config import EXPORT_DIR
def ensure_export_directory(subdir=None):
"""Assure que le répertoire d'export existe"""
if subdir:
directory = os.path.join(EXPORT_DIR, subdir)
else:
directory = EXPORT_DIR
os.makedirs(directory, exist_ok=True)
return directory
def save_json(filename, data):
"""Sauvegarde des données au format JSON"""
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return True
except Exception as e:
print(f"Erreur lors de la sauvegarde du fichier {filename}: {e}")
return False
def get_user_choice(prompt, options):
"""Obtient un choix utilisateur parmi une liste d'options"""
while True:
print(prompt)
for i, option in enumerate(options, 1):
print(f"{i}. {option}")
try:
choice = int(input("Votre choix: "))
if 1 <= choice <= len(options):
return choice
else:
print("Choix invalide. Veuillez réessayer.")
except ValueError:
print("Veuillez entrer un nombre valide.")

0
utils.py:Zone.Identifier Normal file
View File