diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97f1089 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/odtkit diff --git a/.requirements.txt b/.requirements.txt index e69de29..fbcc3d6 100644 --- a/.requirements.txt +++ b/.requirements.txt @@ -0,0 +1,2 @@ +odoorpc +bs4 \ No newline at end of file diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..40c910c Binary files /dev/null and b/__pycache__/config.cpython-312.pyc differ diff --git a/__pycache__/data_filter.cpython-312.pyc b/__pycache__/data_filter.cpython-312.pyc new file mode 100644 index 0000000..0d7596d Binary files /dev/null and b/__pycache__/data_filter.cpython-312.pyc differ diff --git a/__pycache__/menu_handlers.cpython-312.pyc b/__pycache__/menu_handlers.cpython-312.pyc new file mode 100644 index 0000000..c4a3838 Binary files /dev/null and b/__pycache__/menu_handlers.cpython-312.pyc differ diff --git a/__pycache__/menu_principal.cpython-312.pyc b/__pycache__/menu_principal.cpython-312.pyc new file mode 100644 index 0000000..3d9d557 Binary files /dev/null and b/__pycache__/menu_principal.cpython-312.pyc differ diff --git a/__pycache__/odoo_connection.cpython-312.pyc b/__pycache__/odoo_connection.cpython-312.pyc new file mode 100644 index 0000000..77f5042 Binary files /dev/null and b/__pycache__/odoo_connection.cpython-312.pyc differ diff --git a/__pycache__/ticket_manager.cpython-312.pyc b/__pycache__/ticket_manager.cpython-312.pyc new file mode 100644 index 0000000..a9123ee Binary files /dev/null and b/__pycache__/ticket_manager.cpython-312.pyc differ diff --git a/__pycache__/utils.cpython-312.pyc b/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000..f3cad0f Binary files /dev/null and b/__pycache__/utils.cpython-312.pyc differ diff --git a/menu_handlers.py b/menu_handlers.py index 747e676..2d9f4aa 100644 --- a/menu_handlers.py +++ b/menu_handlers.py @@ -1,4 +1,5 @@ from ticket_manager import TicketManager +from utils import get_user_choice # Initialisation de l'objet ticket_manager = TicketManager() diff --git a/ticket_manager.py b/ticket_manager.py index 7c468b8..00159e4 100644 --- a/ticket_manager.py +++ b/ticket_manager.py @@ -1,7 +1,7 @@ from odoo_connection import OdooConnection import os import json -from utils import save_json, ensure_export_directory +from utils import save_json, ensure_export_directory, print_error from config import EXPORT_DIR from data_filter import filter_ticket_data @@ -14,32 +14,57 @@ class TicketManager: 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 + def _ensure_connection(self): + """Vérifie et établit la onnexion si nécessaire""" + if not self.odoo: + self.odoo = self.conn.get_odoo_instance() 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(): + """ + Exécute une méthode Odoo en toute sécurité via odoorpc. + Vérifie la connexion avant d'exécuter. + """ + # Vérifier que la connexion est bien établie + if not self._ensure_connection(): + print_error("Connexion Odoo indisponible.") return None + try: - return self.odoo.execute(model, method, *args) - except Exception as e: - print(f"Erreur lors de {method} sur {model}: {e}") + # Exécuter la méthode sur le modèle via OdooRPC + return self.odoo.execute(model, method, *args) # type: ignore + + except odoorpc.error.RPCError as e: # type: ignore + print_error(f" Erreur RPC lors de '{method}' sur '{model}': {e}") return None + except Exception as e: + print_error(f" Erreur inattendue lors de '{method}' sur '{model}': {e}") + return None + + def get_ticket_by_id(self, ticket_id): + """ Récupère les détails d'un ticket par son ID et applique le filtre """ + fields_to_read = ['id', 'name', 'code', 'stage_id', 'date_deadline', 'description', 'message_ids'] + + # Vérifier la connexion avant d'exécuter la requête + if not self._ensure_connection(): + print_error("Connexion Odoo indisponible.") + return None + + # Récupérer les données du ticket + ticket_data = self._safe_execute(self.model_name, 'read', [ticket_id], fields_to_read) + + if not ticket_data: + print_error(f"Aucun ticket trouvé avec l'ID {ticket_id}") + return None + + # Nettoyer et filtrer les données du ticket + return filter_ticket_data(ticket_data[0]) # Utilisation de data_filter.py 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.") + print_error("Aucun modèle disponible.") return [] print("\nListe des modèles disponibles:") @@ -51,7 +76,7 @@ class TicketManager: """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}.") + print_error(f"Aucun champ trouvé pour le modèle {model_name}.") return [] print(f"\nChamps du modèle {model_name}:") @@ -59,73 +84,78 @@ class TicketManager: 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") + def export_model_fields_to_json(self, model_name, filename): + """Exporte les champs d'un modèle dans un fichier JSON""" + fields_info = self._safe_execute(model_name, 'fields_get') + if not fields_info: + print_error(f"Aucun champ trouvé pour le modèle {model_name}.") return + data = {field_name: field_data['type'] for field_name, field_data in fields_info.items()} + filepath = os.path.join(EXPORT_DIR, filename) + + if save_json(filepath, data): + print(f"Champs du modèle {model_name} exportés dans {filepath}") + else: + print_error(f"Erreur lors de l'exportation des champs du modèle {model_name} dans {filepath}") + + def export_tickets_by_project_and_stage(self, project_id, selected_stage_ids=None): + """ Exporte les tickets d'un projet classés par étape """ + + # Vérifier la connexion Odoo + if not self._ensure_connection(): + print_error("Connexion Odoo indisponible.") + return + + # Récupérer le projet depuis Odoo + project_data = self._safe_execute('project.project', 'search_read', [('id', '=', project_id)], ['id', 'name']) + if not project_data: + print_error(f"Projet ID {project_id} introuvable.") + return + project_name = project_data[0]['name'] + + # Construire le domaine de recherche des tickets domain = [('project_id', '=', project_id)] if selected_stage_ids: domain.append(('stage_id', 'in', selected_stage_ids)) - + + # Récupérer les tickets du projet ticket_ids = self._safe_execute(self.model_name, 'search', domain, 0, 1000) if not ticket_ids: - print("Aucun ticket trouvé") + print_error("Aucun ticket trouvé pour ce projet.") return - - tickets = [self.get_ticket_by_id(ticket_id) for ticket_id in ticket_ids] - + + # Lire les détails des tickets + tickets = [self.get_ticket_by_id(ticket_id) for ticket_id in ticket_ids if self.get_ticket_by_id(ticket_id)] + + # Trier les tickets par étape 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] + #Vérifier que ticket est bien un dictionnaire + if not isinstance(ticket, dict): + print_error("Erreur: le format du ticket est invalide.") + return None + # Vérifier que "Champs Relationnels" existe + champs_relationnels = ticket.get("Champs Relationnels", {}) + + # Vérifier que "stage_id" existe et est une liste avec au moins 2 éléments + stage_data = champs_relationnels.get("stage_id", [0, "Non classé"]) + stage_id = stage_data[0] if isinstance(stage_data, list) and len(stage_data) > 0 else 0 + stage_name = stage_data[1] if isinstance(stage_data, list) and len(stage_data) > 1 else "Non classé" + key = f"{stage_id}_{stage_name}" - if key not in tickets_by_stage: tickets_by_stage[key] = [] tickets_by_stage[key].append(ticket) - + + # Créer le répertoire du projet project_dir = ensure_export_directory(f"project_{project_id}_{project_name.replace(' ', '_')}") - + + # Sauvegarder les tickets par étape 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}/") + + print(f"Exportation terminée. Les fichiers sont enregistrés dans : {project_dir}/") diff --git a/utils.py b/utils.py index fe484c9..54baa54 100644 --- a/utils.py +++ b/utils.py @@ -35,4 +35,7 @@ def get_user_choice(prompt, options): else: print("Choix invalide. Veuillez réessayer.") except ValueError: - print("Veuillez entrer un nombre valide.") \ No newline at end of file + print("Veuillez entrer un nombre valide.") + +def print_error(message): + print(f" Erreur: {message}") \ No newline at end of file