From 6bb1253a1abf4d5828b511a82107563f07c13f8d Mon Sep 17 00:00:00 2001 From: fanilo Date: Sun, 28 Dec 2025 21:40:27 +0100 Subject: [PATCH] Better usage and exploitation of contact_to_dict --- sage_connector.py | 178 +++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 104 deletions(-) diff --git a/sage_connector.py b/sage_connector.py index 298baa0..ed0cd02 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -74,13 +74,6 @@ class SageConnector: if conn: conn.close() -# def _safe_strip(self, value): -# """Strip sécurisé pour valeurs SQL""" -# if value is None: -# return None -# if isinstance(value, str): -# return value.strip() -# return value def _cleanup_com_thread(self): """Nettoie COM pour le thread actuel (à appeler à la fin)""" @@ -937,7 +930,6 @@ class SageConnector: cursor.execute(query, [numero]) rows = cursor.fetchall() - # Récupérer le contact par défaut du client query_client = """ SELECT CT_Contact FROM F_COMPTET @@ -954,7 +946,6 @@ class SageConnector: for row in rows: contact = self._row_to_contact_dict(row) - # Vérifier si c'est le contact par défaut if nom_contact_defaut: nom_complet = f"{contact.get('prenom', '')} {contact['nom']}".strip() contact["est_defaut"] = ( @@ -6666,7 +6657,7 @@ class SageConnector: def creer_contact(self, contact_data: Dict) -> Dict: """ Crée un nouveau contact dans F_CONTACTT via COM - VERSION CORRIGÉE - Utilise IBOTiersContact3 + VERSION FINALE COMPLÈTE """ if not self.cial: raise RuntimeError("Connexion Sage non établie") @@ -6717,7 +6708,7 @@ class SageConnector: persist = factory_contact.Create() logger.info(f" Objet cree: {type(persist).__name__}") - # Cast vers IBOTiersContact3 (qui a Nom, Prenom, etc.) + # Cast vers IBOTiersContact3 contact = None interfaces_a_tester = [ "IBOTiersContact3", @@ -6730,12 +6721,11 @@ class SageConnector: try: temp = win32com.client.CastTo(persist, interface_name) - # Vérifier si Nom existe (pas CT_Num !) + # Vérifier si Nom existe if hasattr(temp, '_prop_map_put_'): props = list(temp._prop_map_put_.keys()) logger.info(f" Test {interface_name}: props={props[:15]}") - # Chercher Nom ou CT_Nom if 'Nom' in props or 'CT_Nom' in props: contact = temp logger.info(f" OK Cast reussi vers {interface_name}") @@ -6930,24 +6920,22 @@ class SageConnector: logger.info(f" CT_No={contact_no}") logger.info("=" * 80) - # Retour - contact_dict = { - "numero": numero_client, - "n_contact": n_contact or contact_no, - "civilite": contact_data.get("civilite"), - "nom": nom, - "prenom": prenom, - "fonction": contact_data.get("fonction"), - "service_code": contact_data.get("service_code"), - "telephone": contact_data.get("telephone"), - "portable": contact_data.get("portable"), - "telecopie": contact_data.get("telecopie"), - "email": contact_data.get("email"), - "facebook": contact_data.get("facebook"), - "linkedin": contact_data.get("linkedin"), - "skype": contact_data.get("skype"), - "est_defaut": est_defaut, - } + # Utiliser _contact_to_dict + contact_dict = self._contact_to_dict( + contact, + numero_client=numero_client, + contact_numero=contact_no, + n_contact=n_contact + ) + contact_dict["est_defaut"] = est_defaut + + logger.info("=" * 80) + logger.info("[DEBUG RETOUR]") + logger.info(f" numero_client = {numero_client}") + logger.info(f" contact_no = {contact_no}") + logger.info(f" n_contact = {n_contact}") + logger.info(f" contact_dict COMPLET = {contact_dict}") + logger.info("=" * 80) return contact_dict @@ -6958,11 +6946,10 @@ class SageConnector: logger.error(f"[ERREUR] {e}", exc_info=True) raise RuntimeError(f"Erreur technique: {e}") - def modifier_contact(self, numero: str, contact_numero: int, updates: Dict) -> Dict: """ Modifie un contact existant via COM - VERSION REFACTORISÉE - Utilise FactoryTiersContact + VERSION COMPLÈTE REFACTORISÉE """ if not self.cial: raise RuntimeError("Connexion Sage non établie") @@ -6987,58 +6974,44 @@ class SageConnector: except Exception as e: raise ValueError(f"Client {numero} introuvable: {e}") - # Charger le contact via FactoryTiersContact + # Charger le contact via SQL puis DossierContact logger.info("[2] Chargement du contact") - if not hasattr(client_obj, 'FactoryTiersContact'): - raise RuntimeError("FactoryTiersContact non trouvee sur le client") - - factory_contact = client_obj.FactoryTiersContact + nom_recherche = None + prenom_recherche = None try: - # Chercher le contact par son CT_No - # Il faut lister et trouver celui avec le bon CT_No - # ou utiliser une autre méthode de lecture - - # STRATÉGIE : Lister tous les contacts et trouver le bon - contacts_collection = None - all_contacts = [] - - # Essayer de lister via SQL d'abord (plus fiable) - try: - with self._get_sql_connection() as conn: - cursor = conn.cursor() - cursor.execute( - "SELECT CT_Nom, CT_Prenom FROM F_CONTACTT WHERE CT_Num = ? AND CT_No = ?", - [numero, contact_numero] - ) - row = cursor.fetchone() - - if not row: - raise ValueError(f"Contact CT_No={contact_numero} non trouve") - - nom_recherche = row.CT_Nom.strip() if row.CT_Nom else "" - prenom_recherche = row.CT_Prenom.strip() if row.CT_Prenom else "" - - logger.info(f" Contact trouve en SQL: {prenom_recherche} {nom_recherche}") - - except Exception as e: - raise ValueError(f"Contact introuvable: {e}") - - # Charger via ReadNomPrenom - factory_dossier = self.cial.CptaApplication.FactoryDossierContact - persist = factory_dossier.ReadNomPrenom(nom_recherche, prenom_recherche) - - if not persist: - raise ValueError(f"Contact non trouvable via ReadNomPrenom") - - contact = win32com.client.CastTo(persist, "IBOTiersContact3") - contact.Read() - logger.info(f" OK Contact charge: {contact.Nom}") + # Récupérer nom/prénom via SQL + with self._get_sql_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT CT_Nom, CT_Prenom FROM F_CONTACTT WHERE CT_Num = ? AND CT_No = ?", + [numero, contact_numero] + ) + row = cursor.fetchone() + + if not row: + raise ValueError(f"Contact CT_No={contact_numero} non trouve") + + nom_recherche = row.CT_Nom.strip() if row.CT_Nom else "" + prenom_recherche = row.CT_Prenom.strip() if row.CT_Prenom else "" + + logger.info(f" Contact trouve en SQL: {prenom_recherche} {nom_recherche}") except Exception as e: raise ValueError(f"Contact introuvable: {e}") + # Charger via FactoryDossierContact + factory_dossier = self.cial.CptaApplication.FactoryDossierContact + persist = factory_dossier.ReadNomPrenom(nom_recherche, prenom_recherche) + + if not persist: + raise ValueError(f"Contact non trouvable via ReadNomPrenom") + + contact = win32com.client.CastTo(persist, "IBOTiersContact3") + contact.Read() + logger.info(f" OK Contact charge: {contact.Nom}") + # Appliquer les modifications logger.info("[3] Application des modifications") modifications_appliquees = [] @@ -7207,7 +7180,13 @@ class SageConnector: logger.info(f"[SUCCES] Contact modifie: CT_No={contact_numero}") logger.info("=" * 80) - contact_dict = self._contact_to_dict(contact) + # Utiliser _contact_to_dict + contact_dict = self._contact_to_dict( + contact, + numero_client=numero, + contact_numero=contact_numero, + n_contact=None + ) contact_dict["est_defaut"] = est_actuellement_defaut return contact_dict @@ -7218,12 +7197,12 @@ class SageConnector: except Exception as e: logger.error(f"[ERREUR] {e}", exc_info=True) raise RuntimeError(f"Erreur technique: {e}") - + def definir_contact_defaut(self, numero: str, contact_numero: int) -> Dict: """ Définit un contact comme contact par défaut du client - VERSION REFACTORISÉE + VERSION COMPLÈTE REFACTORISÉE """ if not self.cial: raise RuntimeError("Connexion Sage non établie") @@ -7325,7 +7304,7 @@ class SageConnector: except Exception as e: logger.error(f"[ERREUR] {e}", exc_info=True) raise RuntimeError(f"Erreur technique: {e}") - + def lister_contacts(self, numero: str) -> List[Dict]: """ @@ -7383,7 +7362,6 @@ class SageConnector: try: with self._com_context(), self._lock_com: - # Charger le client factory_client = self.cial.CptaApplication.FactoryClient persist_client = factory_client.ReadNumero(numero) @@ -7393,7 +7371,6 @@ class SageConnector: client = win32com.client.CastTo(persist_client, "IBOClient3") client.Read() - # Méthode 1: Via CT_NoContact (si disponible) ct_no_defaut = None try: ct_no_defaut = getattr(client, "CT_NoContact", None) @@ -7402,7 +7379,6 @@ class SageConnector: except: pass - # Méthode 2: Via CT_Contact (nom) nom_contact_defaut = None try: nom_contact_defaut = getattr(client, "CT_Contact", None) @@ -7411,11 +7387,9 @@ class SageConnector: except: pass - # Si on a le CT_No, on retourne le contact complet if ct_no_defaut: return self.obtenir_contact(numero, ct_no_defaut) - # Sinon, chercher par nom dans la liste des contacts if nom_contact_defaut: contacts = self.lister_contacts(numero) for contact in contacts: @@ -7433,7 +7407,7 @@ class SageConnector: def supprimer_contact(self, numero: str, contact_numero: int) -> Dict: """ Supprime un contact via COM - VERSION REFACTORISÉE + VERSION COMPLÈTE REFACTORISÉE """ if not self.cial: raise RuntimeError("Connexion Sage non établie") @@ -7521,15 +7495,19 @@ class SageConnector: except Exception as e: logger.error(f"[ERREUR] {e}", exc_info=True) raise RuntimeError(f"Erreur technique: {e}") - - def _contact_to_dict(self, contact) -> Dict: + def _contact_to_dict(self, contact, numero_client=None, contact_numero=None, n_contact=None) -> Dict: """ Convertit un objet COM Contact (IBOTiersContact3) en dictionnaire - VERSION REFACTORISÉE + + Args: + contact: Objet COM contact + numero_client: Code du client (optionnel) + contact_numero: CT_No du contact (optionnel) + n_contact: N_Contact du contact (optionnel) """ try: - # IBOTiersContact3 utilise Nom/Prenom (sans préfixe CT_) + # Civilité civilite_code = getattr(contact, "Civilite", None) civilite_map = {0: "M.", 1: "Mme", 2: "Mlle", 3: "Société"} civilite = civilite_map.get(civilite_code) if civilite_code is not None else None @@ -7550,19 +7528,10 @@ class SageConnector: except: pass - # Récupérer CT_No et N_Contact via SQL si possible - # Car IBOTiersContact3 du DossierContact ne les a pas - contact_numero = None - n_contact = None - numero = None - - # Ces infos doivent venir de F_CONTACTT - # On les passe plutôt en paramètre ou on les récupère différemment - return { - "numero": numero, # À passer en paramètre - "contact_numero": contact_numero, # À passer en paramètre - "n_contact": n_contact, # À passer en paramètre + "numero": numero_client, + "contact_numero": contact_numero, + "n_contact": n_contact or contact_numero, "civilite": civilite, "nom": self._safe_strip(getattr(contact, "Nom", None)), "prenom": self._safe_strip(getattr(contact, "Prenom", None)), @@ -7579,7 +7548,8 @@ class SageConnector: except Exception as e: logger.warning(f"Erreur conversion contact: {e}") return {} - + + def _row_to_contact_dict(self, row) -> Dict: """Convertit une ligne SQL en dictionnaire contact""" civilite_code = row.CT_Civilite