From f339433b0f297caf62ecf3366f6acb20b90e08a2 Mon Sep 17 00:00:00 2001 From: Ladebeze66 Date: Wed, 19 Mar 2025 17:23:19 +0100 Subject: [PATCH] J6-8 --- __pycache__/menu_handlers.cpython-312.pyc | Bin 5627 -> 7391 bytes __pycache__/menu_principal.cpython-312.pyc | Bin 1677 -> 1892 bytes __pycache__/ticket_manager.cpython-312.pyc | Bin 12950 -> 23365 bytes menu_handlers.py | 47 ++++- menu_principal.py | 12 +- ticket_manager.py | 208 ++++++++++++++++++++- 6 files changed, 256 insertions(+), 11 deletions(-) diff --git a/__pycache__/menu_handlers.cpython-312.pyc b/__pycache__/menu_handlers.cpython-312.pyc index a062c4095db0ab35c293ebbaa6c9167fefb70315..656b46b921ec2e6a95a5b2a8229cd0c885d96f65 100644 GIT binary patch delta 1971 zcmah~Z%k8H6o0qvYhPRXm$v*7Op@+@PuIlC=^YzSMBY#B-lA$Bh$4pXs`?Ph(ag zTIi3z(a@WkPA{u&QCpn3h6>Uoqv#6~!wehglo9J^5UEnkv{_q=EcAhSGkQwH+E)^B z?Xazg+?aV&k%?${vxIo#B>NI%SqajTN9q-Gk%_m)HJ2IQ7T5CjDp4 zxLQ}WV26`ioHKRPhaNS-8Rs9deQ z-ZnL}djZikuQ6ji+DErKMDz)rc6`05z@ZW~!;sU#myn50)pyck&M#31{mog2_ET%Q zpFIN0@67j=*J>(4Ag(tRo`1KV(iUSUJ*q$8v?=S456`Mk#T9x}e_DJ4kO$K#D9vz# zFs_hbzP2zg$aqXlr$R6fM?^`1Sy1q(Aj`o)koktFgcUI~A}CQmP?!!#3K3#Ja;OXQ z(dQX^SS>L?;biYO-4shx5BOJSqYi6{Yl?fP-g+5SF8%P&y?y<6iJ~ zMhp#!0+De*;{&wAb>+dktpEDB2j|FLNTyl zLUodl*2j?-Q%-gQPa)$RNVy_opm5$mP*H-Rp;19n5+R5;Y9cs_ryJ zq6*x@051|*{Ty(Ki24=)4#)4mbLc zE6;cU@!h$M>)kutd(WP@nKI8BqX8~3xjM4k{+~GODhmsL$qsOQ)!gA_Zh2iDd$qUP z5sZg@CIV@pvb(ZceGG_N=tQk$qE=%*Z1N^ex+{C$2VMQ?$f_0Y3_|yr#{10XNAPCU q!i!;D)9I0K&F)2>wQgibU1)8a8NH3xs{N=BJswBp=qMs;6#oE`#RA>{ delta 496 zcmca_`CFUsG%qg~0}#x9cq{$2;6y$NMvsZ=o-CCdnw%S}Y?-(@fO5=0{F#Sw@<|D& z$vb&;nf*#QC;KqVPc{^gW^rMN)t=nNtYuch2{Hx*Qn+fEW;3L4*D$B>NCNq@8ERN& zGt6aO%?OreU}WggWnrjfu4LBao!r2BS-yx3C{e@?ByO>kR2HOavfX0M%qu7@xy723 zU!FSo7n?4VChKH9b_3R8R-mfM6WLX{6@UzWATD0SF!><6vfvY5>Fd1a7kSNha9!qg zZg9Ifi9?=+iPM2`vJ!_XJ*neaN34dT^;NU9~1R1wcLTqyw=S@a7 z2_UaXanw%k$ z$(S?wlE^w~h#4S*l!3%8mh9BZ;vyChUuE)a(J72(lMTd_7|kaKi&?R{1BI4v6hx(=0LRxfJ7BQ1jtQAAZv?28jDOO*UCt6c>dgv#?Mk#jJ}Lue*ypyA8*Y7 diff --git a/__pycache__/menu_principal.cpython-312.pyc b/__pycache__/menu_principal.cpython-312.pyc index 5ee4a87cdc6cd7d9e9a8a300d3d1be0f472c8292..393aa6a7c86ae088d79890c5764fde71be05cad2 100644 GIT binary patch delta 651 zcmZuu&1=*^6rZ>G*le=tYEf#x*oc%iuvT%!Ra<)NO)7qYEUh6Vu^XGs%47nqdK6U9 z!!lP79xXiz{sVgvyexFRgyKmLf`ZsX!Hd30t@O|b^P8FXd-Fbi^UC}(WPUVFouFO+ zo~>?o%~3etovu!{)usx>FFYm9qAww88?vo@6M?qcbzWS}PPg3Hj{+xHXVmqW!$NN@ zV2;a}>$TcJ%o0yTADDy(v35Un|GeSjpr6sUV4noS1e2RzI`G~iw*Kxt)S#K(MC#J4 zwJ0iuG_ax|u^xuKw;t{fI$_L$q-M2)Bym^K6EG``?{{e1Wnm{yEWfwO+%SrIn@IZ8 zX>9Gc`bcl^uZ1aSa6?+rD_BG5klf)^T4?n3djr|okxS2O19@b}IMzRYVcV$pgdsifq#~+K*UeURaoYi5wG#Pi1U11uCCOc5MN!JA%q%CB*|^--wq_6K)#ssWhUCm z%|9g5hJMKZUnn3ipVHVqb3TH%oWF3c6M5SG?g44>I3!w&GjSj# z-zCYjk|NdLq_+CyHrZ;cc#R>LfJa<_JM4kZ1NY!sglrRAdm2IllruezO=TX^*j9G+ zp!75u&L%RNjb0_*sns*WoyhW=k?PQtFma#8eFK8$bd~R1$fu8&2oXkI#Xhw(Wkh}i z)zavQG@O&9j>y1uNz5a&&hY_aw5iFYpvLIUsjXv6&ETt=@u!nH+b@C`Vbe+XS2lE6 z@vwGOEIaNVvw}H!>M9{to>iAyNim4OWJeW3U0<-GA^FSXcl6 diff --git a/__pycache__/ticket_manager.cpython-312.pyc b/__pycache__/ticket_manager.cpython-312.pyc index f390b49d54497c177fcedb3c8ad409aa45e03a59..3202604dd101ef501caedd9cb4d4795ba31e0ddc 100644 GIT binary patch delta 9483 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 U;Cl=M^xY7ac$~#HIE@qIU$2Mb%7 diff --git a/menu_handlers.py b/menu_handlers.py index d259bf3..8e0abe0 100644 --- a/menu_handlers.py +++ b/menu_handlers.py @@ -18,12 +18,34 @@ def handle_list_model_fields(): if model_name.lower() == 'q': 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) if fields: print(f"\nChamps du modèle '{model_name}':") for field, info in fields.items(): 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(): """Gère la recherche d'un ticket par ID""" @@ -114,4 +136,25 @@ def handle_project_tickets_by_stage(): return # Exporter les tickets selon les filtres choisis - ticket_manager.export_tickets_by_project_and_stage(project_id, stage_id) \ No newline at end of file + 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}") \ No newline at end of file diff --git a/menu_principal.py b/menu_principal.py index a46f602..62c66a0 100644 --- a/menu_principal.py +++ b/menu_principal.py @@ -3,7 +3,8 @@ from menu_handlers import ( handle_search_ticket_by_id, handle_search_ticket_by_code, handle_list_models, - handle_list_model_fields + handle_list_model_fields, + handle_extract_ticket_attachments ) def display_main_menu(): @@ -14,8 +15,9 @@ def display_main_menu(): print("3. Rechercher un ticket par Code") print("4. Afficher la liste des modèles disponibles") print("5. Afficher les champs d'un modèle donné") - print("6. Quitter") - return input("\nChoisissez une option (1-6): ") + print("6. Extraire les pièces jointes, messages et informations détaillées d'un ticket") + print("7. Quitter") + return input("\nChoisissez une option (1-7): ") def run_menu(): @@ -33,7 +35,9 @@ def run_menu(): elif choice == '5': handle_list_model_fields() elif choice == '6': + handle_extract_ticket_attachments() + elif choice == '7': print("Au revoir!") break else: - print("Option invalide. Veuillez choisir entre 1 et 6.") \ No newline at end of file + print("Option invalide. Veuillez choisir entre 1 et 7.") \ No newline at end of file diff --git a/ticket_manager.py b/ticket_manager.py index 71e3370..e54fc2a 100644 --- a/ticket_manager.py +++ b/ticket_manager.py @@ -1,6 +1,7 @@ from odoo_connection import OdooConnection import os import json +import base64 from utils import print_error from config import EXPORT_DIR @@ -75,17 +76,27 @@ class TicketManager: return models_dict def get_model_fields_with_types(self, model_name): - """Récupère et sauvegarde les champs d'un modèle avec leurs types""" - fields_info = self._safe_execute(model_name, 'fields_get', [], ['name', 'type', 'relation']) + """Récupère et sauvegarde les champs d'un modèle avec tous leurs attributs""" + # Récupérer tous les attributs disponibles pour chaque champ + fields_info = self._safe_execute(model_name, 'fields_get', []) if not fields_info: print_error(f"Impossible de récupérer les champs pour {model_name}") 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 = { field: { - "type": info["type"], - "relation": info.get("relation", None) + "type": info.get("type", "unknown"), + "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() } @@ -255,3 +266,190 @@ class TicketManager: 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 +