This commit is contained in:
Ladebeze66 2025-03-19 17:23:19 +01:00
parent 2e6d83df8a
commit f339433b0f
6 changed files with 256 additions and 11 deletions

View File

@ -18,12 +18,34 @@ def handle_list_model_fields():
if model_name.lower() == 'q': if model_name.lower() == 'q':
return return
# Assurons-nous que model_name est bien une chaîne de caractères
model_name = str(model_name)
fields = ticket_manager.get_model_fields_with_types(model_name) fields = ticket_manager.get_model_fields_with_types(model_name)
if fields: if fields:
print(f"\nChamps du modèle '{model_name}':") print(f"\nChamps du modèle '{model_name}':")
for field, info in fields.items(): for field, info in fields.items():
relation_info = f" (relation avec {info['relation']})" if info["relation"] else "" relation_info = f" (relation avec {info['relation']})" if info["relation"] else ""
print(f"- {field} : {info['type']}{relation_info}") required_info = " [Obligatoire]" if info.get("required") else ""
readonly_info = " [Lecture seule]" if info.get("readonly") else ""
print(f"- {field} : {info['type']}{relation_info}{required_info}{readonly_info}")
# Afficher la description du champ si disponible
if info.get("string") and info.get("string") != field:
print(f" Label: {info['string']}")
# Afficher l'aide si disponible
if info.get("help"):
print(f" Description: {info['help']}")
# Afficher les options si c'est un champ de sélection
if info.get("selection"):
print(" Options:")
for key, value in info["selection"]:
print(f" - {key}: {value}")
print("") # Ligne vide pour séparer les champs
def handle_search_ticket_by_id(): def handle_search_ticket_by_id():
"""Gère la recherche d'un ticket par ID""" """Gère la recherche d'un ticket par ID"""
@ -114,4 +136,25 @@ def handle_project_tickets_by_stage():
return return
# Exporter les tickets selon les filtres choisis # Exporter les tickets selon les filtres choisis
ticket_manager.export_tickets_by_project_and_stage(project_id, stage_id) ticket_manager.export_tickets_by_project_and_stage(project_id, stage_id)
def handle_extract_ticket_attachments():
"""Gère l'extraction des pièces jointes et messages d'un ticket"""
# Demander à l'utilisateur d'entrer l'ID du ticket
ticket_id_input = input("\nEntrez l'ID du ticket à extraire (ou 'q' pour quitter): ")
if ticket_id_input.lower() == 'q':
return
try:
ticket_id = int(ticket_id_input)
except ValueError:
print_error("L'ID du ticket doit être un nombre entier.")
return
# Extraire les pièces jointes et les messages
output_dir = ticket_manager.extract_ticket_attachments_and_messages(ticket_id)
if output_dir:
print(f"\nExtraction terminée avec succès. Les fichiers ont été enregistrés dans: {output_dir}")
else:
print_error(f"L'extraction a échoué pour le ticket avec l'ID {ticket_id}")

View File

@ -3,7 +3,8 @@ from menu_handlers import (
handle_search_ticket_by_id, handle_search_ticket_by_id,
handle_search_ticket_by_code, handle_search_ticket_by_code,
handle_list_models, handle_list_models,
handle_list_model_fields handle_list_model_fields,
handle_extract_ticket_attachments
) )
def display_main_menu(): def display_main_menu():
@ -14,8 +15,9 @@ def display_main_menu():
print("3. Rechercher un ticket par Code") print("3. Rechercher un ticket par Code")
print("4. Afficher la liste des modèles disponibles") print("4. Afficher la liste des modèles disponibles")
print("5. Afficher les champs d'un modèle donné") print("5. Afficher les champs d'un modèle donné")
print("6. Quitter") print("6. Extraire les pièces jointes, messages et informations détaillées d'un ticket")
return input("\nChoisissez une option (1-6): ") print("7. Quitter")
return input("\nChoisissez une option (1-7): ")
def run_menu(): def run_menu():
@ -33,7 +35,9 @@ def run_menu():
elif choice == '5': elif choice == '5':
handle_list_model_fields() handle_list_model_fields()
elif choice == '6': elif choice == '6':
handle_extract_ticket_attachments()
elif choice == '7':
print("Au revoir!") print("Au revoir!")
break break
else: else:
print("Option invalide. Veuillez choisir entre 1 et 6.") print("Option invalide. Veuillez choisir entre 1 et 7.")

View File

@ -1,6 +1,7 @@
from odoo_connection import OdooConnection from odoo_connection import OdooConnection
import os import os
import json import json
import base64
from utils import print_error from utils import print_error
from config import EXPORT_DIR from config import EXPORT_DIR
@ -75,17 +76,27 @@ class TicketManager:
return models_dict return models_dict
def get_model_fields_with_types(self, model_name): def get_model_fields_with_types(self, model_name):
"""Récupère et sauvegarde les champs d'un modèle avec leurs types""" """Récupère et sauvegarde les champs d'un modèle avec tous leurs attributs"""
fields_info = self._safe_execute(model_name, 'fields_get', [], ['name', 'type', 'relation']) # Récupérer tous les attributs disponibles pour chaque champ
fields_info = self._safe_execute(model_name, 'fields_get', [])
if not fields_info: if not fields_info:
print_error(f"Impossible de récupérer les champs pour {model_name}") print_error(f"Impossible de récupérer les champs pour {model_name}")
return {} return {}
# Construire un dictionnaire {champ: {type, relation (si relationnel)}} # Sauvegarde en JSON (tous les attributs sont conservés)
self.save_raw_ticket_data(fields_info, f"fields_{model_name}_complete.json")
print(f"Liste complète des champs du modèle '{model_name}' sauvegardée dans 'fields_{model_name}_complete.json'")
# Pour la compatibilité, construire aussi le dictionnaire simplifié
fields_dict = { fields_dict = {
field: { field: {
"type": info["type"], "type": info.get("type", "unknown"),
"relation": info.get("relation", None) "relation": info.get("relation", None),
"string": info.get("string", field),
"help": info.get("help", ""),
"required": info.get("required", False),
"readonly": info.get("readonly", False),
"selection": info.get("selection", []) if info.get("type") == "selection" else None
} }
for field, info in fields_info.items() for field, info in fields_info.items()
} }
@ -255,3 +266,190 @@ class TicketManager:
print(f"Exportation terminée. Les fichiers sont organisés dans : {export_base_dir}/") print(f"Exportation terminée. Les fichiers sont organisés dans : {export_base_dir}/")
def extract_ticket_attachments_and_messages(self, ticket_id):
"""
Extrait toutes les pièces jointes et messages d'un ticket
et les sauvegarde dans un répertoire dédié.
Args:
ticket_id: ID du ticket à extraire
"""
# Récupérer les informations du ticket
ticket = self.get_ticket_by_id(ticket_id)
if not ticket:
print_error(f"Impossible de trouver le ticket avec l'ID {ticket_id}")
return
# Créer un répertoire pour ce ticket
ticket_name = ticket.get('name', 'Sans nom').replace('/', '_').replace('\\', '_')
ticket_dir = os.path.join(EXPORT_DIR, f"ticket_{ticket_id}_{ticket_name}")
os.makedirs(ticket_dir, exist_ok=True)
# Sauvegarder les données du ticket
self.save_raw_ticket_data(ticket, os.path.join(ticket_dir, "ticket_info.json"))
# Récupérer les informations de contact supplémentaires si disponibles
contact_info = {}
if ticket.get('partner_id'):
partner_id = ticket.get('partner_id')[0] if isinstance(ticket.get('partner_id'), list) else ticket.get('partner_id')
partner_details = self._safe_execute('res.partner', 'read', [partner_id],
['name', 'email', 'phone', 'mobile', 'street', 'city', 'zip', 'country_id', 'comment'])
if partner_details:
contact_info = partner_details[0]
self.save_raw_ticket_data(contact_info, os.path.join(ticket_dir, "contact_info.json"))
print(f"Informations de contact extraites pour le partenaire {partner_id}")
# Récupérer les activités liées au ticket
activity_ids = ticket.get('activity_ids', [])
if activity_ids:
activities = self._safe_execute('mail.activity', 'read', activity_ids,
['id', 'res_id', 'activity_type_id', 'summary', 'note', 'date_deadline', 'user_id', 'create_date'])
if activities:
self.save_raw_ticket_data(activities, os.path.join(ticket_dir, "activities.json"))
print(f"{len(activities)} activités extraites pour le ticket {ticket_id}")
# Récupérer les messages (discussions)
message_ids = ticket.get('message_ids', [])
if message_ids:
# Récupérer tous les messages avec leurs détails
messages = self._safe_execute('mail.message', 'read', message_ids,
['id', 'body', 'date', 'author_id', 'email_from', 'subject', 'parent_id', 'message_type', 'subtype_id', 'attachment_ids'])
if messages:
# Sauvegarder tous les messages dans un fichier
self.save_raw_ticket_data(messages, os.path.join(ticket_dir, "messages.json"))
# Organiser les messages par fil de discussion
messages_by_thread = {}
for message in messages:
parent_id = message.get('parent_id', False)
parent_id = parent_id[0] if isinstance(parent_id, list) and len(parent_id) > 0 else False
if not parent_id: # Message principal
thread_id = message['id']
if thread_id not in messages_by_thread:
messages_by_thread[thread_id] = {
'main_message': message,
'replies': []
}
else: # Réponse à un message
if parent_id not in messages_by_thread:
messages_by_thread[parent_id] = {
'main_message': None,
'replies': []
}
messages_by_thread[parent_id]['replies'].append(message)
# Sauvegarder les fils de discussion
self.save_raw_ticket_data(messages_by_thread, os.path.join(ticket_dir, "message_threads.json"))
# Sauvegarder chaque message dans un fichier séparé pour une meilleure lisibilité
messages_dir = os.path.join(ticket_dir, "messages")
os.makedirs(messages_dir, exist_ok=True)
for message in messages:
message_id = message.get('id', 0)
message_date = message.get('date', '').replace(':', '-').replace(' ', '_')
message_path = os.path.join(messages_dir, f"message_{message_id}_{message_date}.json")
# Récupérer les détails de l'auteur si disponible
if message.get('author_id') and isinstance(message.get('author_id'), list) and len(message.get('author_id')) > 0:
author_id = message.get('author_id')[0]
author_details = self._safe_execute('res.partner', 'read', [author_id], ['name', 'email', 'phone', 'function', 'company_id'])
if author_details:
message['author_details'] = author_details[0]
# Récupérer les détails du sous-type si disponible
if message.get('subtype_id') and isinstance(message.get('subtype_id'), list) and len(message.get('subtype_id')) > 0:
subtype_id = message.get('subtype_id')[0]
subtype_details = self._safe_execute('mail.message.subtype', 'read', [subtype_id], ['name', 'description', 'default'])
if subtype_details:
message['subtype_details'] = subtype_details
with open(message_path, "w", encoding="utf-8") as f:
json.dump(message, f, indent=4, ensure_ascii=False)
print(f"{len(messages)} messages extraits pour le ticket {ticket_id}")
# Récupérer les suiveurs du ticket
follower_ids = ticket.get('message_follower_ids', [])
if follower_ids:
followers = self._safe_execute('mail.followers', 'read', follower_ids, ['id', 'partner_id', 'name', 'email', 'subtype_ids'])
if followers:
# Enrichir les informations des suiveurs
for follower in followers:
if follower.get('partner_id') and isinstance(follower.get('partner_id'), list) and len(follower.get('partner_id')) > 0:
partner_id = follower.get('partner_id')[0]
partner_details = self._safe_execute('res.partner', 'read', [partner_id], ['name', 'email', 'phone', 'function', 'company_id'])
if partner_details:
follower['partner_details'] = partner_details[0]
# Récupérer les détails des sous-types
if follower.get('subtype_ids'):
subtype_details = self._safe_execute('mail.message.subtype', 'read', follower.get('subtype_ids'), ['name', 'description', 'default'])
if subtype_details:
follower['subtype_details'] = subtype_details
self.save_raw_ticket_data(followers, os.path.join(ticket_dir, "followers.json"))
print(f"{len(followers)} suiveurs extraits pour le ticket {ticket_id}")
# Récupérer les pièces jointes
attachment_ids = self._safe_execute('ir.attachment', 'search',
[('res_model', '=', self.model_name), ('res_id', '=', ticket_id)])
if attachment_ids:
attachments = self._safe_execute('ir.attachment', 'read', attachment_ids,
['id', 'name', 'datas', 'mimetype', 'create_date', 'create_uid', 'description', 'res_name', 'public', 'type'])
if attachments:
# Sauvegarder les métadonnées des pièces jointes
attachments_info = [{
'id': att.get('id'),
'name': att.get('name'),
'mimetype': att.get('mimetype'),
'create_date': att.get('create_date'),
'description': att.get('description'),
'res_name': att.get('res_name'),
'type': att.get('type'),
'file_path': f"attachments/{att.get('id')}_{att.get('name', '').replace('/', '_')}"
} for att in attachments]
self.save_raw_ticket_data(attachments_info, os.path.join(ticket_dir, "attachments_info.json"))
# Créer un répertoire pour les pièces jointes
attachments_dir = os.path.join(ticket_dir, "attachments")
os.makedirs(attachments_dir, exist_ok=True)
# Sauvegarder chaque pièce jointe
for attachment in attachments:
attachment_id = attachment.get('id', 0)
attachment_name = attachment.get('name', f"attachment_{attachment_id}").replace('/', '_')
attachment_data = attachment.get('datas')
if attachment_data:
try:
# Décoder les données base64
binary_data = base64.b64decode(attachment_data)
file_path = os.path.join(attachments_dir, f"{attachment_id}_{attachment_name}")
with open(file_path, "wb") as f:
f.write(binary_data)
print(f"Pièce jointe {attachment_name} sauvegardée dans {file_path}")
except Exception as e:
print_error(f"Erreur lors de la sauvegarde de la pièce jointe {attachment_name}: {e}")
print(f"{len(attachments)} pièces jointes extraites pour le ticket {ticket_id}")
# Extraire les historiques de timesheet si disponibles
timesheet_ids = ticket.get('timesheet_ids', [])
if timesheet_ids:
timesheets = self._safe_execute('account.analytic.line', 'read', timesheet_ids,
['id', 'date', 'user_id', 'name', 'unit_amount', 'employee_id', 'department_id'])
if timesheets:
self.save_raw_ticket_data(timesheets, os.path.join(ticket_dir, "timesheets.json"))
print(f"{len(timesheets)} feuilles de temps extraites pour le ticket {ticket_id}")
print(f"Extraction terminée. Les fichiers sont disponibles dans: {ticket_dir}")
return ticket_dir