From 53517136f24d2c5294aa13dc83844cf784040950 Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Fri, 5 Dec 2025 22:41:33 +0300 Subject: [PATCH] fix(sage_connector): improve client creation with better error handling --- sage_connector.py | 62 +++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/sage_connector.py b/sage_connector.py index e922a0e..3812b26 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -2378,31 +2378,38 @@ class SageConnector: def creer_client(self, client_data: Dict) -> Dict: """ Crée un nouveau client dans Sage 100c via l'API COM. - ✅ CORRECTION : Cast obligatoire après Create() + ✅ CORRECTION : CT_Type est en lecture seule, défini automatiquement par la factory """ if not self.cial: raise RuntimeError("Connexion Sage non établie") try: with self._com_context(), self._lock_com: - # 1. Accès à la Factory Client + # 1. Accès à la Factory Client (définit automatiquement CT_Type = 0) factory_client = self.cial.CptaApplication.FactoryClient - # ✅ Create() retourne IBIPersistObject (générique) + # Create() retourne IBIPersistObject persist = factory_client.Create() if not persist: raise RuntimeError("Factory.Create() a retourné None") - logger.debug(f"📦 Objet IBIPersistObject créé : {type(persist)}") + logger.debug(f"📦 Objet IBIPersistObject créé") - # ✅ CAST OBLIGATOIRE vers IBOClient3 pour accéder aux propriétés métier + # Cast vers IBOClient3 try: client = win32com.client.CastTo(persist, "IBOClient3") logger.debug(f"✅ Cast réussi vers IBOClient3") except Exception as e: raise RuntimeError(f"Impossible de caster vers IBOClient3: {e}") + # Vérifier que CT_Type est bien défini automatiquement + try: + ct_type_auto = getattr(client, "CT_Type", None) + logger.debug(f"✅ CT_Type défini automatiquement par factory: {ct_type_auto}") + except Exception as e: + logger.warning(f"⚠️ Impossible de lire CT_Type: {e}") + # 2. Remplissage des Champs OBLIGATOIRES logger.info(f"📝 Création client: {client_data['intitule']}") @@ -2413,12 +2420,8 @@ class SageConnector: except Exception as e: raise RuntimeError(f"Impossible de définir CT_Intitule: {e}") - # CT_Type - OBLIGATOIRE - try: - client.CT_Type = 0 # 0 = Client - logger.debug("✅ CT_Type = 0 (Client)") - except Exception as e: - raise RuntimeError(f"Impossible de définir CT_Type: {e}") + # ❌ CT_Type - NE PAS DÉFINIR (lecture seule, auto-défini par factory) + # La FactoryClient définit automatiquement CT_Type = 0 # CT_Num (Numéro de compte) - Optionnel si auto-numérotation num_prop = client_data.get("num", "") @@ -2429,7 +2432,7 @@ class SageConnector: except Exception as e: logger.warning(f"⚠️ Impossible de définir CT_Num: {e}") - # ✅ Compte comptable général + # Compte comptable général try: if client_data.get("compte_collectif"): client.CT_CompteG = client_data["compte_collectif"] @@ -2459,7 +2462,7 @@ class SageConnector: # 3. CHAMPS OPTIONNELS - # --- Adresse principale (Accès via sous-objet Adresse) --- + # --- Adresse principale --- try: adresse_obj = getattr(client, "Adresse", None) if adresse_obj: @@ -2475,7 +2478,7 @@ class SageConnector: except Exception as e: logger.warning(f"⚠️ Impossible de définir l'adresse: {e}") - # --- Contact / Télécom (Accès via sous-objet Telecom) --- + # --- Contact / Télécom --- try: telecom_obj = getattr(client, "Telecom", None) if telecom_obj: @@ -2502,12 +2505,22 @@ class SageConnector: logger.info("💾 Écriture du client dans Sage...") try: client.Write() + logger.debug("✅ Write() réussi") except Exception as e: - raise RuntimeError(f"Échec Write(): {e}") + # Récupérer l'erreur Sage détaillée si disponible + 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 Write(): {error_detail}") - # ✅ IMPORTANT: Relire pour récupérer le numéro auto-généré + # 5. Relire pour récupérer le numéro auto-généré try: client.Read() + logger.debug("✅ Read() après Write() réussi") except Exception as e: logger.warning(f"⚠️ Impossible de relire après Write(): {e}") @@ -2516,7 +2529,7 @@ class SageConnector: if not num_final: raise RuntimeError("CT_Num vide après Write() - création échouée") - # Si CT_NumPayeur était vide, le définir maintenant avec le numéro auto-généré + # Si CT_NumPayeur était vide, le définir maintenant if not num_prop: try: client.CT_NumPayeur = num_final @@ -2525,15 +2538,16 @@ class SageConnector: except Exception as e: logger.warning(f"⚠️ Impossible de définir CT_NumPayeur après création: {e}") - logger.info(f"✅ Client créé avec succès: {num_final} - {client.CT_Intitule}") + logger.info(f"✅✅✅ Client créé avec succès: {num_final} - {client.CT_Intitule} ✅✅✅") - # ✅ Forcer le refresh du cache pour que le nouveau client soit visible + # 6. Forcer le refresh du cache self._refresh_cache_clients() return { "numero": num_final, "intitule": client.CT_Intitule, "compte_collectif": getattr(client, "CT_CompteG", ""), + "type": getattr(client, "CT_Type", 0), # Retourner le type auto-défini "adresse": client_data.get("adresse"), "ville": client_data.get("ville"), "email": client_data.get("email") @@ -2542,15 +2556,17 @@ class SageConnector: except Exception as e: logger.error(f"❌ Erreur création client: {e}", exc_info=True) - # Gestion d'erreur remontant l'erreur COM détaillée (si disponible) + # Gestion d'erreur avec détails Sage error_message = str(e) if self.cial: try: - err = self.cial.LastError + err = self.cial.CptaApplication.LastError if err: error_message = f"Erreur Sage: {err.Description}" - if "doublon" in err.Description.lower(): - raise ValueError(f"Ce client ou ce numéro existe déjà. {error_message}") + if "doublon" in err.Description.lower() or "existe" in err.Description.lower(): + raise ValueError(f"Ce client existe déjà dans Sage. {error_message}") + except ValueError: + raise # Re-lever les ValueError pour différencier erreurs métier vs techniques except: pass