Trying to resolve error on creating client
This commit is contained in:
parent
9d4f620bdc
commit
015943bdfa
1 changed files with 186 additions and 108 deletions
|
|
@ -2378,54 +2378,87 @@ 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.
|
||||||
✅ Gestion stricte des longueurs de champs
|
✅ Validation STRICTE des longueurs + Diagnostic exhaustif
|
||||||
"""
|
"""
|
||||||
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. Factory Client
|
# ========================================
|
||||||
|
# ÉTAPE 0 : VALIDATION PRÉALABLE
|
||||||
|
# ========================================
|
||||||
|
logger.info("🔍 === VALIDATION DES DONNÉES ===")
|
||||||
|
|
||||||
|
# Intitulé obligatoire
|
||||||
|
if not client_data.get("intitule"):
|
||||||
|
raise ValueError("Le champ 'intitule' est obligatoire")
|
||||||
|
|
||||||
|
# Tronquer TOUS les champs texte
|
||||||
|
intitule = str(client_data["intitule"])[:69].strip()
|
||||||
|
num_prop = str(client_data.get("num", "")).upper()[:17].strip()
|
||||||
|
compte = str(client_data.get("compte_collectif", "411000"))[:13].strip()
|
||||||
|
|
||||||
|
adresse = str(client_data.get("adresse", ""))[:35].strip()
|
||||||
|
code_postal = str(client_data.get("code_postal", ""))[:9].strip()
|
||||||
|
ville = str(client_data.get("ville", ""))[:35].strip()
|
||||||
|
pays = str(client_data.get("pays", ""))[:35].strip()
|
||||||
|
|
||||||
|
telephone = str(client_data.get("telephone", ""))[:21].strip()
|
||||||
|
email = str(client_data.get("email", ""))[:69].strip()
|
||||||
|
|
||||||
|
siret = str(client_data.get("siret", ""))[:14].strip()
|
||||||
|
tva_intra = str(client_data.get("tva_intra", ""))[:25].strip()
|
||||||
|
|
||||||
|
# Log des valeurs tronquées
|
||||||
|
logger.info(f" intitule: '{intitule}' (len={len(intitule)}/69)")
|
||||||
|
logger.info(f" num: '{num_prop or 'AUTO'}' (len={len(num_prop)}/17)")
|
||||||
|
logger.info(f" compte: '{compte}' (len={len(compte)}/13)")
|
||||||
|
logger.info(f" adresse: '{adresse}' (len={len(adresse)}/35)")
|
||||||
|
logger.info(f" code_postal: '{code_postal}' (len={len(code_postal)}/9)")
|
||||||
|
logger.info(f" ville: '{ville}' (len={len(ville)}/35)")
|
||||||
|
logger.info(f" pays: '{pays}' (len={len(pays)}/35)")
|
||||||
|
logger.info(f" telephone: '{telephone}' (len={len(telephone)}/21)")
|
||||||
|
logger.info(f" email: '{email}' (len={len(email)}/69)")
|
||||||
|
logger.info(f" siret: '{siret}' (len={len(siret)}/14)")
|
||||||
|
logger.info(f" tva_intra: '{tva_intra}' (len={len(tva_intra)}/25)")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# ÉTAPE 1 : CRÉATION OBJET CLIENT
|
||||||
|
# ========================================
|
||||||
factory_client = self.cial.CptaApplication.FactoryClient
|
factory_client = self.cial.CptaApplication.FactoryClient
|
||||||
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")
|
||||||
|
|
||||||
# Cast vers IBOClient3
|
|
||||||
client = win32com.client.CastTo(persist, "IBOClient3")
|
client = win32com.client.CastTo(persist, "IBOClient3")
|
||||||
logger.debug(f"✅ Cast réussi vers IBOClient3")
|
logger.info("✅ Objet client créé")
|
||||||
|
|
||||||
# 2. Remplissage avec VALIDATION DES LONGUEURS
|
# ========================================
|
||||||
logger.info(f"📝 Création client: {client_data['intitule']}")
|
# ÉTAPE 2 : CHAMPS OBLIGATOIRES MINIMAUX
|
||||||
|
# ========================================
|
||||||
|
logger.info("📝 Définition champs obligatoires...")
|
||||||
|
|
||||||
# ===== CT_Intitule - OBLIGATOIRE (max 69 car) =====
|
# 1. Intitulé (OBLIGATOIRE)
|
||||||
intitule = client_data["intitule"][:69] # Tronquer si nécessaire
|
|
||||||
try:
|
|
||||||
client.CT_Intitule = intitule
|
client.CT_Intitule = intitule
|
||||||
logger.debug(f"✅ CT_Intitule: {intitule}")
|
logger.debug(f" ✅ CT_Intitule: '{intitule}'")
|
||||||
except Exception as e:
|
|
||||||
raise RuntimeError(f"Impossible de définir CT_Intitule: {e}")
|
|
||||||
|
|
||||||
# ===== CT_Num - Optionnel (max 17 car, ALPHA MAJ) =====
|
# 2. Type (OBLIGATOIRE - 0=Client)
|
||||||
num_prop = client_data.get("num", "")
|
client.CT_Type = 0
|
||||||
if num_prop:
|
logger.debug(" ✅ CT_Type: 0 (Client)")
|
||||||
num_prop = num_prop.upper()[:17] # Majuscules + tronquer
|
|
||||||
try:
|
|
||||||
client.CT_Num = num_prop
|
|
||||||
logger.debug(f"✅ CT_Num: {num_prop}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"⚠️ CT_Num non défini: {e}")
|
|
||||||
|
|
||||||
# ===== CT_CompteG - Compte collectif (max 13 car) =====
|
# 3. Compte collectif
|
||||||
compte = client_data.get("compte_collectif", "411000")[:13]
|
if compte:
|
||||||
try:
|
|
||||||
client.CT_CompteG = compte
|
client.CT_CompteG = compte
|
||||||
logger.debug(f"✅ CT_CompteG: {compte}")
|
logger.debug(f" ✅ CT_CompteG: '{compte}'")
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"⚠️ CT_CompteG non défini: {e}")
|
|
||||||
|
|
||||||
# ===== Catégories (valeurs numériques, pas de pb de longueur) =====
|
# 4. Numéro client (optionnel, auto si vide)
|
||||||
|
if num_prop:
|
||||||
|
client.CT_Num = num_prop
|
||||||
|
logger.debug(f" ✅ CT_Num: '{num_prop}'")
|
||||||
|
|
||||||
|
# 5. Catégories (valeurs par défaut)
|
||||||
try:
|
try:
|
||||||
client.N_CatTarif = 1
|
client.N_CatTarif = 1
|
||||||
client.N_CatCompta = 1
|
client.N_CatCompta = 1
|
||||||
|
|
@ -2437,93 +2470,130 @@ class SageConnector:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f" ⚠️ Catégories: {e}")
|
logger.warning(f" ⚠️ Catégories: {e}")
|
||||||
|
|
||||||
# ===== CT_NumPayeur (max 17 car) =====
|
# ========================================
|
||||||
if num_prop:
|
# ÉTAPE 3 : CHAMPS OPTIONNELS
|
||||||
try:
|
# ========================================
|
||||||
client.CT_NumPayeur = num_prop # Déjà tronqué plus haut
|
logger.info("📝 Définition champs optionnels...")
|
||||||
logger.debug(f"✅ CT_NumPayeur: {num_prop}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"⚠️ CT_NumPayeur: {e}")
|
|
||||||
|
|
||||||
# ===== ADRESSE (tous max 35 car sauf code postal = 9) =====
|
# Adresse
|
||||||
|
if any([adresse, code_postal, ville, pays]):
|
||||||
try:
|
try:
|
||||||
adresse_obj = getattr(client, "Adresse", None)
|
adresse_obj = getattr(client, "Adresse", None)
|
||||||
if adresse_obj:
|
if adresse_obj:
|
||||||
if client_data.get("adresse"):
|
if adresse:
|
||||||
adresse_obj.Adresse = client_data["adresse"][:35]
|
adresse_obj.Adresse = adresse
|
||||||
if client_data.get("code_postal"):
|
if code_postal:
|
||||||
adresse_obj.CodePostal = client_data["code_postal"][:9]
|
adresse_obj.CodePostal = code_postal
|
||||||
if client_data.get("ville"):
|
if ville:
|
||||||
adresse_obj.Ville = client_data["ville"][:35]
|
adresse_obj.Ville = ville
|
||||||
if client_data.get("pays"):
|
if pays:
|
||||||
adresse_obj.Pays = client_data["pays"][:35]
|
adresse_obj.Pays = pays
|
||||||
logger.debug(" ✅ Adresse définie")
|
logger.debug(" ✅ Adresse définie")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f" ⚠️ Adresse: {e}")
|
logger.warning(f" ⚠️ Adresse: {e}")
|
||||||
|
|
||||||
# ===== TELECOM (Téléphone max 21, Email max 69) =====
|
# Télécom
|
||||||
|
if telephone or email:
|
||||||
try:
|
try:
|
||||||
telecom_obj = getattr(client, "Telecom", None)
|
telecom_obj = getattr(client, "Telecom", None)
|
||||||
if telecom_obj:
|
if telecom_obj:
|
||||||
if client_data.get("telephone"):
|
if telephone:
|
||||||
telecom_obj.Telephone = client_data["telephone"][:21]
|
telecom_obj.Telephone = telephone
|
||||||
if client_data.get("email"):
|
if email:
|
||||||
telecom_obj.EMail = client_data["email"][:69]
|
telecom_obj.EMail = email
|
||||||
logger.debug(" ✅ Télécom défini")
|
logger.debug(" ✅ Télécom défini")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f" ⚠️ Télécom: {e}")
|
logger.warning(f" ⚠️ Télécom: {e}")
|
||||||
|
|
||||||
# ===== IDENTIFIANTS (SIRET max 14, TVA max 25) =====
|
# Identifiants
|
||||||
|
if siret:
|
||||||
try:
|
try:
|
||||||
if client_data.get("siret"):
|
client.CT_Siret = siret
|
||||||
client.CT_Siret = client_data["siret"][:14]
|
logger.debug(f" ✅ SIRET: '{siret}'")
|
||||||
logger.debug(f"✅ SIRET: {client_data['siret'][:14]}")
|
|
||||||
if client_data.get("tva_intra"):
|
|
||||||
client.CT_Identifiant = client_data["tva_intra"][:25]
|
|
||||||
logger.debug(f"✅ TVA: {client_data['tva_intra'][:25]}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ Identifiants: {e}")
|
logger.warning(f" ⚠️ SIRET: {e}")
|
||||||
|
|
||||||
# 3. DIAGNOSTIC PRÉ-WRITE (pour debug)
|
if tva_intra:
|
||||||
|
try:
|
||||||
|
client.CT_Identifiant = tva_intra
|
||||||
|
logger.debug(f" ✅ TVA: '{tva_intra}'")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f" ⚠️ TVA: {e}")
|
||||||
|
|
||||||
|
# NumPayeur (= NumClient si défini)
|
||||||
|
if num_prop:
|
||||||
|
try:
|
||||||
|
client.CT_NumPayeur = num_prop
|
||||||
|
logger.debug(f" ✅ CT_NumPayeur: '{num_prop}'")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f" ⚠️ CT_NumPayeur: {e}")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# ÉTAPE 4 : DIAGNOSTIC PRÉ-WRITE
|
||||||
|
# ========================================
|
||||||
logger.info("🔍 === DIAGNOSTIC PRÉ-WRITE ===")
|
logger.info("🔍 === DIAGNOSTIC PRÉ-WRITE ===")
|
||||||
try:
|
|
||||||
logger.info(f" CT_Intitule: '{client.CT_Intitule}' (len={len(client.CT_Intitule)})")
|
|
||||||
logger.info(f" CT_Num: '{getattr(client, 'CT_Num', 'AUTO')}'")
|
|
||||||
logger.info(f" CT_CompteG: '{getattr(client, 'CT_CompteG', 'N/A')}'")
|
|
||||||
logger.info(f" CT_Type: {getattr(client, 'CT_Type', '?')}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"⚠️ Erreur diagnostic: {e}")
|
|
||||||
|
|
||||||
# 4. Écriture en base
|
champs_critiques = [
|
||||||
|
"CT_Intitule", "CT_Num", "CT_Type", "CT_CompteG",
|
||||||
|
"CT_NumPayeur", "N_CatTarif", "N_CatCompta"
|
||||||
|
]
|
||||||
|
|
||||||
|
for champ in champs_critiques:
|
||||||
|
try:
|
||||||
|
val = getattr(client, champ, "N/A")
|
||||||
|
longueur = len(str(val)) if val not in [None, "N/A"] else 0
|
||||||
|
logger.info(f" {champ}: {repr(val)} (len={longueur})")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f" {champ}: Erreur lecture - {e}")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# ÉTAPE 5 : ÉCRITURE EN BASE
|
||||||
|
# ========================================
|
||||||
logger.info("💾 Écriture du client dans Sage...")
|
logger.info("💾 Écriture du client dans Sage...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.Write()
|
client.Write()
|
||||||
logger.info("✅ Write() réussi !")
|
logger.info("✅ Write() réussi !")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Récupérer erreur Sage détaillée
|
# Récupérer erreur Sage détaillée
|
||||||
error_detail = str(e)
|
error_detail = str(e)
|
||||||
|
sage_error_desc = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sage_error = self.cial.CptaApplication.LastError
|
sage_error = self.cial.CptaApplication.LastError
|
||||||
if sage_error:
|
if sage_error:
|
||||||
error_detail = f"{sage_error.Description} (Code: {sage_error.Number})"
|
sage_error_desc = sage_error.Description
|
||||||
logger.error(f"❌ Erreur Sage détaillée: {error_detail}")
|
error_detail = f"{sage_error_desc} (Code: {sage_error.Number})"
|
||||||
|
logger.error(f"❌ Erreur Sage: {error_detail}")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Log des valeurs pour debug
|
# Dump COMPLET des champs pour debug
|
||||||
logger.error("❌ ÉCHEC Write() - Dump des champs définis:")
|
logger.error("❌ ÉCHEC Write() - DUMP COMPLET:")
|
||||||
|
|
||||||
for attr in dir(client):
|
for attr in dir(client):
|
||||||
if attr.startswith("CT_") or attr.startswith("N_"):
|
if (attr.startswith("CT_") or
|
||||||
|
attr.startswith("N_") or
|
||||||
|
attr.startswith("cbMarq")):
|
||||||
try:
|
try:
|
||||||
val = getattr(client, attr, None)
|
val = getattr(client, attr, None)
|
||||||
if val and not callable(val):
|
if val is not None and not callable(val):
|
||||||
logger.error(f" {attr}: {val} (len={len(str(val))})")
|
longueur = len(str(val)) if isinstance(val, str) else "N/A"
|
||||||
|
logger.error(f" {attr}: {repr(val)} (type={type(val).__name__}, len={longueur})")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Tester si c'est un doublon
|
||||||
|
if sage_error_desc and ("doublon" in sage_error_desc.lower() or
|
||||||
|
"existe" in sage_error_desc.lower()):
|
||||||
|
raise ValueError(f"Ce client existe déjà: {sage_error_desc}")
|
||||||
|
|
||||||
raise RuntimeError(f"Échec Write(): {error_detail}")
|
raise RuntimeError(f"Échec Write(): {error_detail}")
|
||||||
|
|
||||||
# 5. Relire pour récupérer le numéro auto-généré
|
# ========================================
|
||||||
|
# ÉTAPE 6 : RELECTURE & FINALISATION
|
||||||
|
# ========================================
|
||||||
try:
|
try:
|
||||||
client.Read()
|
client.Read()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -2534,30 +2604,42 @@ class SageConnector:
|
||||||
if not num_final:
|
if not num_final:
|
||||||
raise RuntimeError("CT_Num vide après Write()")
|
raise RuntimeError("CT_Num vide après Write()")
|
||||||
|
|
||||||
# Si CT_NumPayeur était vide, le définir maintenant
|
# Si auto-numérotation, définir CT_NumPayeur maintenant
|
||||||
if not num_prop:
|
if not num_prop and num_final:
|
||||||
try:
|
try:
|
||||||
client.CT_NumPayeur = num_final
|
client.CT_NumPayeur = num_final
|
||||||
client.Write()
|
client.Write()
|
||||||
logger.debug(f"✅ CT_NumPayeur auto-défini: {num_final}")
|
logger.debug(f"✅ CT_NumPayeur auto-défini: {num_final}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ CT_NumPayeur: {e}")
|
logger.warning(f"⚠️ CT_NumPayeur final: {e}")
|
||||||
|
|
||||||
logger.info(f"✅✅✅ CLIENT CRÉÉ: {num_final} - {client.CT_Intitule} ✅✅✅")
|
logger.info(f"✅✅✅ CLIENT CRÉÉ: {num_final} - {intitule} ✅✅✅")
|
||||||
|
|
||||||
# 6. Refresh cache
|
# ========================================
|
||||||
|
# ÉTAPE 7 : REFRESH CACHE
|
||||||
|
# ========================================
|
||||||
self._refresh_cache_clients()
|
self._refresh_cache_clients()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"numero": num_final,
|
"numero": num_final,
|
||||||
"intitule": client.CT_Intitule,
|
"intitule": intitule,
|
||||||
"compte_collectif": getattr(client, "CT_CompteG", ""),
|
"compte_collectif": compte,
|
||||||
"type": getattr(client, "CT_Type", 0),
|
"type": 0,
|
||||||
"adresse": client_data.get("adresse"),
|
"adresse": adresse or None,
|
||||||
"ville": client_data.get("ville"),
|
"code_postal": code_postal or None,
|
||||||
"email": client_data.get("email")
|
"ville": ville or None,
|
||||||
|
"pays": pays or None,
|
||||||
|
"email": email or None,
|
||||||
|
"telephone": telephone or None,
|
||||||
|
"siret": siret or None,
|
||||||
|
"tva_intra": tva_intra or None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
# Erreur métier (doublon, validation)
|
||||||
|
logger.error(f"❌ Erreur métier: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -2567,10 +2649,6 @@ class SageConnector:
|
||||||
err = self.cial.CptaApplication.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() or "existe" in err.Description.lower():
|
|
||||||
raise ValueError(f"Ce client existe déjà. {error_message}")
|
|
||||||
except ValueError:
|
|
||||||
raise
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue