fix(sage_connector): improve client creation with better error handling

This commit is contained in:
Fanilo-Nantenaina 2025-12-05 22:41:33 +03:00
parent c48a3f033a
commit 53517136f2

View file

@ -2378,31 +2378,38 @@ class SageConnector:
def creer_client(self, client_data: Dict) -> Dict: def creer_client(self, client_data: Dict) -> Dict:
""" """
Crée un nouveau client dans Sage 100c via l'API COM. 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: if not self.cial:
raise RuntimeError("Connexion Sage non établie") raise RuntimeError("Connexion Sage non établie")
try: try:
with self._com_context(), self._lock_com: 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 factory_client = self.cial.CptaApplication.FactoryClient
# Create() retourne IBIPersistObject (générique) # Create() retourne IBIPersistObject
persist = factory_client.Create() persist = factory_client.Create()
if not persist: if not persist:
raise RuntimeError("Factory.Create() a retourné None") 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: try:
client = win32com.client.CastTo(persist, "IBOClient3") client = win32com.client.CastTo(persist, "IBOClient3")
logger.debug(f"✅ Cast réussi vers IBOClient3") logger.debug(f"✅ Cast réussi vers IBOClient3")
except Exception as e: except Exception as e:
raise RuntimeError(f"Impossible de caster vers IBOClient3: {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 # 2. Remplissage des Champs OBLIGATOIRES
logger.info(f"📝 Création client: {client_data['intitule']}") logger.info(f"📝 Création client: {client_data['intitule']}")
@ -2413,12 +2420,8 @@ class SageConnector:
except Exception as e: except Exception as e:
raise RuntimeError(f"Impossible de définir CT_Intitule: {e}") raise RuntimeError(f"Impossible de définir CT_Intitule: {e}")
# CT_Type - OBLIGATOIRE # ❌ CT_Type - NE PAS DÉFINIR (lecture seule, auto-défini par factory)
try: # La FactoryClient définit automatiquement CT_Type = 0
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_Num (Numéro de compte) - Optionnel si auto-numérotation # CT_Num (Numéro de compte) - Optionnel si auto-numérotation
num_prop = client_data.get("num", "") num_prop = client_data.get("num", "")
@ -2429,7 +2432,7 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Impossible de définir CT_Num: {e}") logger.warning(f"⚠️ Impossible de définir CT_Num: {e}")
# Compte comptable général # Compte comptable général
try: try:
if client_data.get("compte_collectif"): if client_data.get("compte_collectif"):
client.CT_CompteG = client_data["compte_collectif"] client.CT_CompteG = client_data["compte_collectif"]
@ -2459,7 +2462,7 @@ class SageConnector:
# 3. CHAMPS OPTIONNELS # 3. CHAMPS OPTIONNELS
# --- Adresse principale (Accès via sous-objet Adresse) --- # --- Adresse principale ---
try: try:
adresse_obj = getattr(client, "Adresse", None) adresse_obj = getattr(client, "Adresse", None)
if adresse_obj: if adresse_obj:
@ -2475,7 +2478,7 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Impossible de définir l'adresse: {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: try:
telecom_obj = getattr(client, "Telecom", None) telecom_obj = getattr(client, "Telecom", None)
if telecom_obj: if telecom_obj:
@ -2502,12 +2505,22 @@ class SageConnector:
logger.info("💾 Écriture du client dans Sage...") logger.info("💾 Écriture du client dans Sage...")
try: try:
client.Write() client.Write()
logger.debug("✅ Write() réussi")
except Exception as e: 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: try:
client.Read() client.Read()
logger.debug("✅ Read() après Write() réussi")
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Impossible de relire après Write(): {e}") logger.warning(f"⚠️ Impossible de relire après Write(): {e}")
@ -2516,7 +2529,7 @@ class SageConnector:
if not num_final: if not num_final:
raise RuntimeError("CT_Num vide après Write() - création échouée") 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: if not num_prop:
try: try:
client.CT_NumPayeur = num_final client.CT_NumPayeur = num_final
@ -2525,15 +2538,16 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Impossible de définir CT_NumPayeur après création: {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() self._refresh_cache_clients()
return { return {
"numero": num_final, "numero": num_final,
"intitule": client.CT_Intitule, "intitule": client.CT_Intitule,
"compte_collectif": getattr(client, "CT_CompteG", ""), "compte_collectif": getattr(client, "CT_CompteG", ""),
"type": getattr(client, "CT_Type", 0), # Retourner le type auto-défini
"adresse": client_data.get("adresse"), "adresse": client_data.get("adresse"),
"ville": client_data.get("ville"), "ville": client_data.get("ville"),
"email": client_data.get("email") "email": client_data.get("email")
@ -2542,15 +2556,17 @@ class SageConnector:
except Exception as e: except Exception as e:
logger.error(f"❌ Erreur création client: {e}", exc_info=True) 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) error_message = str(e)
if self.cial: if self.cial:
try: try:
err = self.cial.LastError err = self.cial.CptaApplication.LastError
if err: if err:
error_message = f"Erreur Sage: {err.Description}" error_message = f"Erreur Sage: {err.Description}"
if "doublon" in err.Description.lower(): if "doublon" in err.Description.lower() or "existe" in err.Description.lower():
raise ValueError(f"Ce client ou ce numéro existe déjà. {error_message}") 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: except:
pass pass