From 3e617d070a55336a94f48f7ecc7a88db19479d98 Mon Sep 17 00:00:00 2001 From: fanilo Date: Sun, 28 Dec 2025 21:31:47 +0100 Subject: [PATCH] Creating new contact successful --- sage_connector.py | 742 ++++++++++++++++++++++++++-------------------- 1 file changed, 420 insertions(+), 322 deletions(-) diff --git a/sage_connector.py b/sage_connector.py index 814a8fa..49111b5 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -6663,29 +6663,10 @@ class SageConnector: return self._lire_document_sql(numero, type_doc=30) - def creer_contact(self, contact_data: Dict) -> Dict: """ - Crée un nouveau contact pour un client via COM - VERSION COMPLÈTE avec gestion du contact par défaut - - Champs Sage Contact (F_CONTACTT): - - CT_Num: Code client parent (obligatoire) - - CT_Nom: Nom (obligatoire, max 35 car) - - CT_Prenom: Prénom (max 35 car) - - CT_Civilite: 0=M., 1=Mme, 2=Mlle, 3=Société - - CT_Fonction: Fonction (max 35 car) - - N_Service: Code service (int) - - CT_Telephone: Tél fixe (max 21 car) - - CT_TelPortable: Mobile (max 21 car) - - CT_Telecopie: Fax (max 21 car) - - CT_EMail: Email (max 69 car) - - CT_Facebook: Facebook (max 69 car) - - CT_LinkedIn: LinkedIn (max 69 car) - - CT_Skype: Skype (max 69 car) - - Nouveau: - - est_defaut: Définir comme contact par défaut (boolean) + Crée un nouveau contact dans F_CONTACTT via COM + VERSION CORRIGÉE - Utilise IBOTiersContact3 """ if not self.cial: raise RuntimeError("Connexion Sage non établie") @@ -6693,130 +6674,206 @@ class SageConnector: try: with self._com_context(), self._lock_com: logger.info("=" * 80) - logger.info("[CRÉATION CONTACT SAGE]") + logger.info("[CREATION CONTACT F_CONTACTT]") logger.info("=" * 80) - # Validation des champs obligatoires + # Validation if not contact_data.get("numero"): raise ValueError("numero (code client) obligatoire") - if not contact_data.get("nom"): raise ValueError("nom obligatoire") - numero = self._clean_str(contact_data["numero"], 17).upper() + numero_client = self._clean_str(contact_data["numero"], 17).upper() nom = self._clean_str(contact_data["nom"], 35) + prenom = self._clean_str(contact_data.get("prenom", ""), 35) - # Vérifier que le client existe - logger.info(f"[1] Vérification client: {numero}") + logger.info(f" CLIENT: {numero_client}") + logger.info(f" CONTACT: {prenom} {nom}") + + # Charger le client + logger.info(f"[1] Chargement du client: {numero_client}") factory_client = self.cial.CptaApplication.FactoryClient try: - persist_client = factory_client.ReadNumero(numero) + persist_client = factory_client.ReadNumero(numero_client) if not persist_client: - raise ValueError(f"Client {numero} non trouvé") - logger.info(f" ✓ Client {numero} existe") + raise ValueError(f"Client {numero_client} non trouve") + + client_obj = win32com.client.CastTo(persist_client, "IBOClient3") + client_obj.Read() + logger.info(f" OK Client charge") except Exception as e: - raise ValueError(f"Client {numero} introuvable: {e}") + raise ValueError(f"Client {numero_client} introuvable: {e}") - # Créer l'objet contact - logger.info("[2] Création objet Contact") - factory_contact = self.cial.CptaApplication.FactoryContactT + # Via FactoryTiersContact du client + logger.info("[2] Creation via FactoryTiersContact") + + if not hasattr(client_obj, 'FactoryTiersContact'): + raise RuntimeError("FactoryTiersContact non trouvee sur le client") + + factory_contact = client_obj.FactoryTiersContact + logger.info(f" OK FactoryTiersContact: {type(factory_contact).__name__}") + + # Créer l'objet persist = factory_contact.Create() - contact = win32com.client.CastTo(persist, "IBOContactT3") - logger.info(" ✓ Objet IBOContactT3 créé") + logger.info(f" Objet cree: {type(persist).__name__}") - # Configuration obligatoire - logger.info("[3] Configuration obligatoire") - contact.CT_Num = numero - contact.CT_Nom = nom - logger.info(f" CT_Num = {numero}") - logger.info(f" CT_Nom = {nom}") + # Cast vers IBOTiersContact3 (qui a Nom, Prenom, etc.) + contact = None + interfaces_a_tester = [ + "IBOTiersContact3", + "IBOTiersContact", + "IBOContactT3", + "IBOContactT", + ] - # SetDefault pour initialiser les valeurs par défaut - contact.SetDefault() - logger.info(" ✓ SetDefault() appliqué") + for interface_name in interfaces_a_tester: + try: + temp = win32com.client.CastTo(persist, interface_name) + + # Vérifier si Nom existe (pas CT_Num !) + 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}") + break + except Exception as e: + logger.debug(f" {interface_name}: {str(e)[:50]}") - # Civilité - logger.info("[4] Identité") - civilite_input = contact_data.get("civilite") - if civilite_input: - civilite_map = { - "M.": 0, - "Mme": 1, - "Mlle": 2, - "Société": 3 - } - civilite_code = civilite_map.get(civilite_input) - if civilite_code is not None: - self._try_set_attribute(contact, "CT_Civilite", civilite_code) - logger.info(f" CT_Civilite = {civilite_code} ({civilite_input})") + if not contact: + logger.error(" ERROR Aucun cast ne fonctionne") + raise RuntimeError("Impossible de caster vers une interface contact valide") + + # Configuration du contact + logger.info("[3] Configuration du contact") + + # Vérifier les propriétés disponibles + if hasattr(contact, '_prop_map_put_'): + props = list(contact._prop_map_put_.keys()) + logger.info(f" Proprietes disponibles: {props}") + + # Nom (obligatoire) + try: + contact.Nom = nom + logger.info(f" OK Nom = {nom}") + except Exception as e: + logger.error(f" ERROR Impossible de definir Nom: {e}") + raise RuntimeError(f"Echec definition Nom: {e}") # Prénom - if contact_data.get("prenom"): - prenom = self._clean_str(contact_data["prenom"], 35) - self._try_set_attribute(contact, "CT_Prenom", prenom) - logger.info(f" CT_Prenom = {prenom}") + if prenom: + try: + contact.Prenom = prenom + logger.info(f" OK Prenom = {prenom}") + except Exception as e: + logger.warning(f" WARN Prenom: {e}") + + # Civilité + if contact_data.get("civilite"): + civilite_map = {"M.": 0, "Mme": 1, "Mlle": 2, "Societe": 3} + civilite_code = civilite_map.get(contact_data["civilite"]) + if civilite_code is not None: + try: + contact.Civilite = civilite_code + logger.info(f" OK Civilite = {civilite_code}") + except Exception as e: + logger.warning(f" WARN Civilite: {e}") # Fonction if contact_data.get("fonction"): fonction = self._clean_str(contact_data["fonction"], 35) - self._try_set_attribute(contact, "CT_Fonction", fonction) - logger.info(f" CT_Fonction = {fonction}") + try: + contact.Fonction = fonction + logger.info(f" OK Fonction = {fonction}") + except Exception as e: + logger.warning(f" WARN Fonction: {e}") # Service - logger.info("[5] Organisation") if contact_data.get("service_code") is not None: - service = self._safe_int(contact_data["service_code"]) - if service is not None: - self._try_set_attribute(contact, "N_Service", service) - logger.info(f" N_Service = {service}") + try: + service = self._safe_int(contact_data["service_code"]) + if service is not None and hasattr(contact, 'ServiceContact'): + contact.ServiceContact = service + logger.info(f" OK ServiceContact = {service}") + except Exception as e: + logger.warning(f" WARN ServiceContact: {e}") - # Coordonnées - logger.info("[6] Coordonnées") + # Telecom + logger.info("[4] Coordonnees (Telecom)") - if contact_data.get("telephone"): - telephone = self._clean_str(contact_data["telephone"], 21) - self._try_set_attribute(contact, "CT_Telephone", telephone) - logger.info(f" CT_Telephone = {telephone}") - - if contact_data.get("portable"): - portable = self._clean_str(contact_data["portable"], 21) - self._try_set_attribute(contact, "CT_TelPortable", portable) - logger.info(f" CT_TelPortable = {portable}") - - if contact_data.get("telecopie"): - fax = self._clean_str(contact_data["telecopie"], 21) - self._try_set_attribute(contact, "CT_Telecopie", fax) - logger.info(f" CT_Telecopie = {fax}") - - if contact_data.get("email"): - email = self._clean_str(contact_data["email"], 69) - self._try_set_attribute(contact, "CT_EMail", email) - logger.info(f" CT_EMail = {email}") + if hasattr(contact, 'Telecom'): + try: + telecom = contact.Telecom + logger.info(f" Type Telecom: {type(telecom).__name__}") + + if contact_data.get("telephone"): + telephone = self._clean_str(contact_data["telephone"], 21) + if self._try_set_attribute(telecom, "Telephone", telephone): + logger.info(f" Telephone = {telephone}") + + if contact_data.get("portable"): + portable = self._clean_str(contact_data["portable"], 21) + if self._try_set_attribute(telecom, "Portable", portable): + logger.info(f" Portable = {portable}") + + if contact_data.get("email"): + email = self._clean_str(contact_data["email"], 69) + if self._try_set_attribute(telecom, "EMail", email): + logger.info(f" EMail = {email}") + + if contact_data.get("telecopie"): + fax = self._clean_str(contact_data["telecopie"], 21) + if self._try_set_attribute(telecom, "Telecopie", fax): + logger.info(f" Telecopie = {fax}") + + except Exception as e: + logger.warning(f" WARN Erreur Telecom: {e}") # Réseaux sociaux - logger.info("[7] Réseaux sociaux") + logger.info("[5] Reseaux sociaux") if contact_data.get("facebook"): facebook = self._clean_str(contact_data["facebook"], 69) - self._try_set_attribute(contact, "CT_Facebook", facebook) - logger.info(f" CT_Facebook = {facebook}") + try: + contact.Facebook = facebook + logger.info(f" Facebook = {facebook}") + except: + pass if contact_data.get("linkedin"): linkedin = self._clean_str(contact_data["linkedin"], 69) - self._try_set_attribute(contact, "CT_LinkedIn", linkedin) - logger.info(f" CT_LinkedIn = {linkedin}") + try: + contact.LinkedIn = linkedin + logger.info(f" LinkedIn = {linkedin}") + except: + pass if contact_data.get("skype"): skype = self._clean_str(contact_data["skype"], 69) - self._try_set_attribute(contact, "CT_Skype", skype) - logger.info(f" CT_Skype = {skype}") + try: + contact.Skype = skype + logger.info(f" Skype = {skype}") + except: + pass - # Enregistrement du contact - logger.info("[8] WRITE CONTACT") + # SetDefault + try: + contact.SetDefault() + logger.info(" OK SetDefault() applique") + except Exception as e: + logger.warning(f" WARN SetDefault(): {e}") + + # Enregistrer + logger.info("[6] Enregistrement du contact") try: contact.Write() + logger.info(" OK Write() reussi") + contact.Read() - logger.info(" ✓ Write() réussi") + logger.info(" OK Read() reussi") except Exception as e: error_detail = str(e) try: @@ -6825,59 +6882,72 @@ class SageConnector: error_detail = f"{sage_error.Description} (Code: {sage_error.Number})" except: pass - logger.error(f" ✗ Erreur Write: {error_detail}") - raise RuntimeError(f"Échec création contact: {error_detail}") + logger.error(f" ERROR Write: {error_detail}") + raise RuntimeError(f"Echec enregistrement: {error_detail}") - # Récupération des données finales - contact_numero = getattr(contact, "CT_No", None) - n_contact = getattr(contact, "N_Contact", None) + # Récupérer les IDs + contact_no = None + n_contact = None + try: + contact_no = getattr(contact, 'CT_No', None) + n_contact = getattr(contact, 'N_Contact', None) + logger.info(f" Contact CT_No={contact_no}, N_Contact={n_contact}") + except: + pass - logger.info(f" Contact créé: CT_No={contact_numero}, N_Contact={n_contact}") - - # Gestion du contact par défaut + # Contact par défaut est_defaut = contact_data.get("est_defaut", False) - - if est_defaut: - logger.info("[9] Définition comme contact par défaut") + if est_defaut and (contact_no or n_contact): + logger.info("[7] Definition comme contact par defaut") try: - if contact_numero: - # Construire le nom complet pour CT_Contact - prenom = self._clean_str(contact_data.get("prenom", ""), 35) - nom_complet = f"{prenom} {nom}".strip() if prenom else nom - - # Charger le client pour mise à jour - persist_client = factory_client.ReadNumero(numero) - client_obj = win32com.client.CastTo(persist_client, "IBOClient3") - client_obj.Read() - - # Définir CT_Contact - ancien_contact = getattr(client_obj, "CT_Contact", "") - client_obj.CT_Contact = nom_complet - logger.info(f" CT_Contact: '{ancien_contact}' → '{nom_complet}'") - - # Essayer CT_NoContact si disponible - if self._try_set_attribute(client_obj, "CT_NoContact", contact_numero): - logger.info(f" CT_NoContact = {contact_numero}") - - # Enregistrer le client - client_obj.Write() - client_obj.Read() - logger.info(f" ✓ Contact défini comme par défaut") - + nom_complet = f"{prenom} {nom}".strip() if prenom else nom + + persist_client = factory_client.ReadNumero(numero_client) + client_obj = win32com.client.CastTo(persist_client, "IBOClient3") + client_obj.Read() + + client_obj.CT_Contact = nom_complet + logger.info(f" CT_Contact = '{nom_complet}'") + + if contact_no and hasattr(client_obj, 'CT_NoContact'): + try: + client_obj.CT_NoContact = contact_no + logger.info(f" CT_NoContact = {contact_no}") + except: + pass + + client_obj.Write() + client_obj.Read() + logger.info(" OK Contact par defaut defini") except Exception as e: - logger.warning(f" ⚠ Échec définition par défaut: {e}") - # On ne fait pas échouer la création pour autant - est_defaut = False # On indique que ça n'a pas marché + logger.warning(f" WARN Echec: {e}") + est_defaut = False logger.info("=" * 80) - logger.info(f"[SUCCÈS] Contact créé: CT_No={contact_numero}, N_Contact={n_contact}") - if est_defaut: - logger.info(f" Défini comme contact par défaut") + logger.info(f"[SUCCES] Contact cree: {prenom} {nom}") + logger.info(f" Lie au client {numero_client}") + if contact_no: + logger.info(f" CT_No={contact_no}") logger.info("=" * 80) - # Retourner les données complètes - contact_dict = self._contact_to_dict(contact) - contact_dict["est_defaut"] = est_defaut + # 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, + } return contact_dict @@ -6892,7 +6962,7 @@ class SageConnector: def modifier_contact(self, numero: str, contact_numero: int, updates: Dict) -> Dict: """ Modifie un contact existant via COM - VERSION COMPLÈTE avec gestion du contact par défaut + VERSION CORRIGÉE pour Sage 100 """ if not self.cial: raise RuntimeError("Connexion Sage non établie") @@ -6903,18 +6973,53 @@ class SageConnector: logger.info(f"[MODIFICATION CONTACT] CT_No={contact_numero}") logger.info("=" * 80) - # Lire le contact existant + # Lire le contact existant - CORRECTION ICI logger.info("[1] Lecture contact existant") - factory_contact = self.cial.CptaApplication.FactoryContactT + + # Trouver la factory + factory_contact = None + factory_names = [ + "FactoryContact", + "FactoryContactTiers", + "FactoryTypeContacts", + ] + + for factory_name in factory_names: + try: + if hasattr(self.cial.CptaApplication, factory_name): + factory_contact = getattr(self.cial.CptaApplication, factory_name) + break + except: + continue + + if not factory_contact: + raise RuntimeError("Aucune factory de contacts trouvée") try: - persist = factory_contact.ReadNumero(numero, contact_numero) + # ReadNumero peut accepter 1 ou 2 paramètres selon la version + try: + persist = factory_contact.ReadNumero(numero, contact_numero) + except TypeError: + # Si 2 paramètres ne fonctionnent pas, essayer avec CT_No seulement + persist = factory_contact.ReadNumero(contact_numero) + if not persist: raise ValueError(f"Contact CT_No={contact_numero} non trouvé pour client {numero}") - contact = win32com.client.CastTo(persist, "IBOContactT3") + # Essayer de caster + contact = None + for interface_name in ["IBOContactT3", "IBOContact3", "IBOContactT"]: + try: + contact = win32com.client.CastTo(persist, interface_name) + break + except: + continue + + if not contact: + contact = persist + contact.Read() - logger.info(f" ✓ Contact chargé: {contact.CT_Nom}") + logger.info(f" ✓ Contact chargé: {getattr(contact, 'CT_Nom', '?')}") except Exception as e: raise ValueError(f"Contact introuvable: {e}") @@ -6934,7 +7039,7 @@ class SageConnector: if "nom" in updates: nom = self._clean_str(updates["nom"], 35) - if nom: # Ne pas autoriser nom vide + if nom: self._try_set_attribute(contact, "CT_Nom", nom) logger.info(f" CT_Nom = {nom}") modifications_appliquees.append("nom") @@ -6992,7 +7097,10 @@ class SageConnector: logger.info("[3] WRITE CONTACT") try: contact.Write() - contact.Read() + try: + contact.Read() + except: + pass logger.info(" ✓ Write() réussi") except Exception as e: error_detail = str(e) @@ -7011,46 +7119,37 @@ class SageConnector: est_defaut_demande = updates.get("est_defaut") est_actuellement_defaut = False - if est_defaut_demande is not None: + if est_defaut_demande is not None and est_defaut_demande: logger.info("[4] Gestion contact par défaut") - - if est_defaut_demande: - # Définir comme contact par défaut - try: - # Construire le nom complet - nom_final = getattr(contact, "CT_Nom", "") - prenom_final = getattr(contact, "CT_Prenom", "") - nom_complet = f"{prenom_final} {nom_final}".strip() if prenom_final else nom_final - - # Charger le client - factory_client = self.cial.CptaApplication.FactoryClient - persist_client = factory_client.ReadNumero(numero) - client_obj = win32com.client.CastTo(persist_client, "IBOClient3") - client_obj.Read() - - # Mettre à jour CT_Contact - ancien_contact = getattr(client_obj, "CT_Contact", "") - client_obj.CT_Contact = nom_complet - logger.info(f" CT_Contact: '{ancien_contact}' → '{nom_complet}'") - - # Essayer CT_NoContact si disponible - if self._try_set_attribute(client_obj, "CT_NoContact", contact_numero): - logger.info(f" CT_NoContact = {contact_numero}") - - # Enregistrer le client - client_obj.Write() - client_obj.Read() - logger.info(" ✓ Contact défini comme par défaut") - est_actuellement_defaut = True - - except Exception as e: - logger.warning(f" ⚠ Échec définition par défaut: {e}") - est_actuellement_defaut = False - else: - # Retirer le statut par défaut - # Note: On ne fait rien car Sage gère via CT_Contact du client - # Si on veut vraiment retirer, il faut définir un autre contact comme par défaut - logger.info(" ⓘ Pour retirer le statut par défaut, définir un autre contact") + try: + # Construire le nom complet + nom_final = getattr(contact, "CT_Nom", "") + prenom_final = getattr(contact, "CT_Prenom", "") + nom_complet = f"{prenom_final} {nom_final}".strip() if prenom_final else nom_final + + # Charger le client + factory_client = self.cial.CptaApplication.FactoryClient + persist_client = factory_client.ReadNumero(numero) + client_obj = win32com.client.CastTo(persist_client, "IBOClient3") + client_obj.Read() + + # Mettre à jour CT_Contact + ancien_contact = getattr(client_obj, "CT_Contact", "") + client_obj.CT_Contact = nom_complet + logger.info(f" CT_Contact: '{ancien_contact}' → '{nom_complet}'") + + # Essayer CT_NoContact si disponible + if self._try_set_attribute(client_obj, "CT_NoContact", contact_numero): + logger.info(f" CT_NoContact = {contact_numero}") + + # Enregistrer le client + client_obj.Write() + client_obj.Read() + logger.info(" ✓ Contact défini comme par défaut") + est_actuellement_defaut = True + + except Exception as e: + logger.warning(f" ⚠ Échec définition par défaut: {e}") else: # Vérifier si c'est déjà le contact par défaut try: @@ -7065,9 +7164,8 @@ class SageConnector: nom_complet = f"{prenom_final} {nom_final}".strip() if prenom_final else nom_final est_actuellement_defaut = (ct_contact == nom_complet) - - except Exception as e: - logger.debug(f"Impossible de vérifier statut par défaut: {e}") + except: + pass logger.info("=" * 80) logger.info(f"[SUCCÈS] Contact modifié: CT_No={contact_numero}") @@ -7075,7 +7173,6 @@ class SageConnector: logger.info(f" Contact par défaut") logger.info("=" * 80) - # Retourner les données mises à jour contact_dict = self._contact_to_dict(contact) contact_dict["est_defaut"] = est_actuellement_defaut @@ -7087,6 +7184,133 @@ 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 CORRIGÉE pour Sage 100 + """ + if not self.cial: + raise RuntimeError("Connexion Sage non établie") + + try: + with self._com_context(), self._lock_com: + logger.info("=" * 80) + logger.info(f"[DÉFINIR CONTACT PAR DÉFAUT] Client={numero}, Contact={contact_numero}") + logger.info("=" * 80) + + # Charger le contact - CORRECTION ICI + logger.info("[1] Chargement du contact") + + # Trouver la factory + factory_contact = None + for factory_name in ["FactoryContact", "FactoryContactTiers", "FactoryTypeContacts"]: + try: + if hasattr(self.cial.CptaApplication, factory_name): + factory_contact = getattr(self.cial.CptaApplication, factory_name) + break + except: + continue + + if not factory_contact: + raise RuntimeError("Factory contacts non trouvée") + + try: + # ReadNumero peut accepter 1 ou 2 paramètres + try: + persist_contact = factory_contact.ReadNumero(numero, contact_numero) + except TypeError: + persist_contact = factory_contact.ReadNumero(contact_numero) + + if not persist_contact: + raise ValueError(f"Contact CT_No={contact_numero} non trouvé") + + # Caster + contact = None + for interface in ["IBOContactT3", "IBOContact3", "IBOContactT"]: + try: + contact = win32com.client.CastTo(persist_contact, interface) + break + except: + continue + + if not contact: + contact = persist_contact + + contact.Read() + + nom_contact = getattr(contact, "CT_Nom", "") + prenom_contact = getattr(contact, "CT_Prenom", "") + nom_complet = f"{prenom_contact} {nom_contact}".strip() if prenom_contact else nom_contact + + logger.info(f" ✓ Contact trouvé: {nom_complet}") + + except Exception as e: + raise ValueError(f"Contact introuvable: {e}") + + # Charger le client + logger.info("[2] Chargement du client") + factory_client = self.cial.CptaApplication.FactoryClient + + try: + persist_client = factory_client.ReadNumero(numero) + if not persist_client: + raise ValueError(f"Client {numero} non trouvé") + + client = win32com.client.CastTo(persist_client, "IBOClient3") + client.Read() + logger.info(f" ✓ Client chargé: {client.CT_Intitule}") + + except Exception as e: + raise ValueError(f"Client introuvable: {e}") + + # Définir le contact par défaut + logger.info("[3] Définition du contact par défaut") + + ancien_contact = getattr(client, "CT_Contact", "") + client.CT_Contact = nom_complet + logger.info(f" CT_Contact: '{ancien_contact}' → '{nom_complet}'") + + # Essayer CT_NoContact si disponible + if self._try_set_attribute(client, "CT_NoContact", contact_numero): + logger.info(f" CT_NoContact = {contact_numero}") + + # Enregistrement + logger.info("[4] WRITE") + try: + client.Write() + client.Read() + logger.info(" ✓ Client mis à jour") + except Exception as e: + error_detail = str(e) + try: + sage_error = self.cial.CptaApplication.LastError + if sage_error: + error_detail = f"{sage_error.Description} (Code: {sage_error.Number})" + except: + pass + raise RuntimeError(f"Échec mise à jour: {error_detail}") + + logger.info("=" * 80) + logger.info(f"[SUCCÈS] Contact par défaut: {nom_complet}") + logger.info("=" * 80) + + return { + "numero": numero, + "contact_numero": contact_numero, + "contact_nom": nom_complet, + "client_intitule": client.CT_Intitule, + "est_defaut": True, + "date_modification": datetime.now().isoformat() + } + + except ValueError as e: + logger.error(f"[ERREUR VALIDATION] {e}") + raise + 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]: @@ -7257,132 +7481,6 @@ class SageConnector: 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 - - Sage gère le contact par défaut via : - 1. CT_Contact dans F_COMPTET : Nom du contact principal (35 car) - 2. CT_NoContact dans F_COMPTET : Numéro du contact par défaut (si disponible) - - Cette méthode : - - Vérifie que le contact existe - - Met à jour le client (F_COMPTET) pour référencer ce contact - - Retourne les infos du client et contact mis à jour - """ - if not self.cial: - raise RuntimeError("Connexion Sage non établie") - - try: - with self._com_context(), self._lock_com: - logger.info("=" * 80) - logger.info(f"[DÉFINIR CONTACT PAR DÉFAUT] Client={numero}, Contact={contact_numero}") - logger.info("=" * 80) - - # Étape 1: Charger le contact - logger.info("[1] Chargement du contact") - factory_contact = self.cial.CptaApplication.FactoryContactT - - try: - persist_contact = factory_contact.ReadNumero(numero, contact_numero) - if not persist_contact: - raise ValueError(f"Contact CT_No={contact_numero} non trouvé pour client {numero}") - - contact = win32com.client.CastTo(persist_contact, "IBOContactT3") - contact.Read() - - nom_contact = getattr(contact, "CT_Nom", "") - prenom_contact = getattr(contact, "CT_Prenom", "") - - # Construire le nom complet - nom_complet = f"{prenom_contact} {nom_contact}".strip() if prenom_contact else nom_contact - - logger.info(f" ✓ Contact trouvé: {nom_complet}") - - except Exception as e: - raise ValueError(f"Contact introuvable: {e}") - - # Étape 2: Charger le client - logger.info("[2] Chargement du client") - factory_client = self.cial.CptaApplication.FactoryClient - - try: - persist_client = factory_client.ReadNumero(numero) - if not persist_client: - raise ValueError(f"Client {numero} non trouvé") - - client = win32com.client.CastTo(persist_client, "IBOClient3") - client.Read() - - logger.info(f" ✓ Client chargé: {client.CT_Intitule}") - - except Exception as e: - raise ValueError(f"Client introuvable: {e}") - - # Étape 3: Définir le contact par défaut - logger.info("[3] Définition du contact par défaut") - - # Méthode 1: Via CT_Contact (nom du contact) - try: - ancien_contact = getattr(client, "CT_Contact", "") - client.CT_Contact = nom_complet - logger.info(f" CT_Contact: '{ancien_contact}' → '{nom_complet}'") - except Exception as e: - logger.warning(f" Impossible de définir CT_Contact: {e}") - - # Méthode 2: Via CT_NoContact (numéro du contact) - si disponible - # Ce champ n'existe pas dans toutes les versions de Sage - if self._try_set_attribute(client, "CT_NoContact", contact_numero): - logger.info(f" CT_NoContact = {contact_numero}") - else: - logger.info(f" CT_NoContact non disponible (normal dans certaines versions)") - - # Méthode 3: Via l'objet Contact (relation) - si disponible - try: - if hasattr(client, "Contact") or hasattr(client, "ContactPrincipal"): - client.Contact = contact - logger.info(f" Contact (objet) défini") - except Exception as e: - logger.debug(f" Contact (objet) non disponible: {e}") - - # Étape 4: Enregistrement - logger.info("[4] WRITE") - try: - client.Write() - client.Read() - logger.info(" ✓ Client mis à jour") - except Exception as e: - error_detail = str(e) - try: - sage_error = self.cial.CptaApplication.LastError - if sage_error: - error_detail = f"{sage_error.Description} (Code: {sage_error.Number})" - except: - pass - logger.error(f" ✗ Erreur Write: {error_detail}") - raise RuntimeError(f"Échec mise à jour client: {error_detail}") - - logger.info("=" * 80) - logger.info(f"[SUCCÈS] Contact par défaut défini: {nom_complet}") - logger.info("=" * 80) - - return { - "numero": numero, - "contact_numero": contact_numero, - "contact_nom": nom_complet, - "client_intitule": client.CT_Intitule, - "est_defaut": True, - "date_modification": datetime.now().isoformat() - } - - except ValueError as e: - logger.error(f"[ERREUR VALIDATION] {e}") - raise - 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: """Convertit un objet COM Contact en dictionnaire""" try: