Added better handling for collaborator

This commit is contained in:
fanilo 2026-01-05 21:00:58 +01:00
parent 69114ba0c3
commit 97a2bc01f0

View file

@ -7390,310 +7390,398 @@ class SageConnector:
return None
def creer_collaborateur(self, data: dict) -> dict:
"""Crée un nouveau collaborateur via COM Sage"""
"""Crée un nouveau collaborateur via COM Sage (BOI)"""
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
logger.info(
f"📝 Début création collaborateur: {data.get('nom')} {data.get('prenom', '')}"
)
# Validation préalable
if not data.get("nom"):
raise ValueError("Le champ 'nom' est obligatoire")
nom_upper = str(data["nom"]).upper().strip()[:35]
prenom = str(data.get("prenom", "")).strip()[:35] if data.get("prenom") else ""
logger.info(f"\n{'=' * 70}")
logger.info(f"📝 CRÉATION COLLABORATEUR: {nom_upper} {prenom}")
logger.info(f"{'=' * 70}")
try:
with self._com_context(), self._lock_com:
transaction_active = False
try:
# Démarrage transaction
try:
self.cial.CptaApplication.BeginTrans()
transaction_active = True
logger.debug("✓ Transaction Sage démarrée")
except Exception as e:
logger.warning(f"BeginTrans échoué (non critique): {e}")
# ===== FACTORY COLLABORATEUR =====
factory_collab = self.cial.FactoryCollaborateur
persist_collab = factory_collab.Create()
# Cast vers l'interface IBOCollaborateur3
try:
collab_obj = win32com.client.CastTo(
persist_collab, "IBOCollaborateur3"
# ===== VÉRIFICATION DOUBLON VIA SQL =====
logger.info("🔍 Vérification doublon...")
with self._get_sql_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT CO_No FROM F_COLLABORATEUR WHERE CO_Nom = ? AND CO_Prenom = ?",
(nom_upper, prenom),
)
except Exception:
# Fallback si IBOCollaborateur3 n'existe pas
collab_obj = win32com.client.CastTo(
persist_collab, "IBOCollaborateur"
existing = cursor.fetchone()
if existing:
raise ValueError(
f"Le collaborateur '{nom_upper} {prenom}' existe déjà (N°{existing[0]})"
)
logger.info("✓ Pas de doublon")
logger.info("✓ Objet collaborateur créé")
# ===== MAPPING ET AFFECTATION CHAMPS =====
mapping = {
"nom": "CO_Nom",
"prenom": "CO_Prenom",
"fonction": "CO_Fonction",
"adresse": "CO_Adresse",
"complement": "CO_Complement",
"code_postal": "CO_CodePostal",
"ville": "CO_Ville",
"region": "CO_CodeRegion",
"pays": "CO_Pays",
"service": "CO_Service",
"telephone": "CO_Telephone",
"telecopie": "CO_Telecopie",
"email": "CO_EMail",
"tel_portable": "CO_TelPortable",
"matricule": "CO_Matricule",
"facebook": "CO_Facebook",
"linkedin": "CO_LinkedIn",
"skype": "CO_Skype",
"chef_ventes_numero": "CO_NoChefVentes",
}
# Champs texte
for py_field, sage_field in mapping.items():
if py_field in data and data[py_field] is not None:
# ===== FACTORY + CREATE =====
try:
setattr(collab_obj, sage_field, str(data[py_field]))
logger.debug(f"{sage_field}: {data[py_field]}")
except Exception as e:
logger.warning(f"{sage_field} non défini: {e}")
factory = self.cial.FactoryCollaborateur
except AttributeError:
factory = self.cial.CptaApplication.FactoryCollaborateur
# ===== CHAMPS BOOLÉENS =====
bool_mapping = {
"est_vendeur": "CO_Vendeur",
"est_caissier": "CO_Caissier",
"est_acheteur": "CO_Acheteur",
"est_chef_ventes": "CO_ChefVentes",
}
persist = factory.Create()
for py_field, sage_field in bool_mapping.items():
if py_field in data:
# Cast vers interface
collab = None
for iface in [
"IBOCollaborateur3",
"IBOCollaborateur2",
"IBOCollaborateur",
]:
try:
valeur = 1 if data[py_field] else 0
setattr(collab_obj, sage_field, valeur)
logger.debug(f"{sage_field}: {valeur}")
except Exception as e:
logger.warning(f"{sage_field} non défini: {e}")
collab = win32com.client.CastTo(persist, iface)
logger.info(f"✓ Cast vers {iface}")
break
except:
pass
if not collab:
collab = persist
# ===== CO_SOMMEIL (inversé: est_actif → 0=actif, 1=inactif) =====
if "est_actif" in data:
# ===== SETDEFAULT =====
try:
valeur_sommeil = 0 if data["est_actif"] else 1
collab_obj.CO_Sommeil = valeur_sommeil
logger.debug(
f" ✓ CO_Sommeil: {valeur_sommeil} (actif={data['est_actif']})"
)
collab.SetDefault()
logger.info("✓ SetDefault()")
except Exception as e:
logger.warning(f" ⚠ CO_Sommeil non défini: {e}")
logger.warning(f"SetDefault() ignoré: {e}")
# ===== HELPER =====
def safe_set(obj, attr, value, max_len=None):
"""Affecte une valeur de manière sécurisée"""
if value is None or value == "":
return False
try:
val = str(value)
if max_len:
val = val[:max_len]
setattr(obj, attr, val)
logger.debug(f"{attr} = '{val}'")
return True
except Exception as e:
logger.warning(f"{attr}: {e}")
return False
# ===== CHAMPS DIRECTS SUR COLLABORATEUR =====
logger.info("📝 Champs directs...")
# Obligatoire
safe_set(collab, "Nom", nom_upper, 35)
# Optionnels
safe_set(collab, "Prenom", prenom, 35)
safe_set(collab, "Fonction", data.get("fonction"), 35)
safe_set(collab, "Service", data.get("service"), 35)
safe_set(collab, "Matricule", data.get("matricule"), 10)
safe_set(collab, "Facebook", data.get("facebook"), 35)
safe_set(collab, "LinkedIn", data.get("linkedin"), 35)
safe_set(collab, "Skype", data.get("skype"), 35)
# ===== SOUS-OBJET ADRESSE =====
logger.info("📍 Adresse...")
try:
adresse_obj = collab.Adresse
safe_set(adresse_obj, "Adresse", data.get("adresse"), 35)
safe_set(adresse_obj, "Complement", data.get("complement"), 35)
safe_set(adresse_obj, "CodePostal", data.get("code_postal"), 9)
safe_set(adresse_obj, "Ville", data.get("ville"), 35)
safe_set(adresse_obj, "CodeRegion", data.get("code_region"), 25)
safe_set(adresse_obj, "Pays", data.get("pays"), 35)
except Exception as e:
logger.warning(f"⚠️ Erreur Adresse: {e}")
# ===== SOUS-OBJET TELECOM =====
logger.info("📞 Telecom...")
try:
telecom_obj = collab.Telecom
safe_set(telecom_obj, "Telephone", data.get("telephone"), 21)
safe_set(telecom_obj, "Telecopie", data.get("telecopie"), 21)
safe_set(telecom_obj, "EMail", data.get("email"), 69)
safe_set(telecom_obj, "Portable", data.get("tel_portable"), 21)
except Exception as e:
logger.warning(f"⚠️ Erreur Telecom: {e}")
# ===== CHAMPS BOOLÉENS (seulement si True) =====
logger.info("🔘 Booléens...")
if data.get("vendeur") is True:
try:
collab.Vendeur = True
logger.debug(" ✓ Vendeur = True")
except:
pass
if data.get("caissier") is True:
try:
collab.Caissier = True
except:
pass
if data.get("acheteur") is True:
try:
collab.Acheteur = True
except:
pass
if data.get("sommeil") is True:
try:
collab.Sommeil = True
except:
pass
if data.get("chef_ventes") is True:
try:
collab.ChefVentes = True
except:
pass
# ===== WRITE =====
collab_obj.Write()
logger.info("💾 Collaborateur écrit dans Sage")
# Commit transaction
if transaction_active:
logger.info("💾 Write()...")
try:
self.cial.CptaApplication.CommitTrans()
logger.info("✓ Transaction committée")
except Exception:
pass
collab.Write()
logger.info("✅ Write() RÉUSSI!")
except Exception as e:
logger.error(f"❌ Write() échoué: {e}")
raise RuntimeError(f"Échec Write(): {e}")
# ===== RÉCUPÉRATION DU NUMÉRO =====
numero_cree = None
# Via Read()
try:
collab_obj.Read()
numero = getattr(collab_obj, "CO_No", None)
except Exception:
numero = None
if not numero:
raise RuntimeError(
"Numéro de collaborateur vide après création"
)
logger.info(f"📄 Collaborateur créé: CO_No={numero}")
# ===== RELECTURE POUR RETOUR COMPLET =====
collaborateur_final = self.lire_collaborateur(numero)
if not collaborateur_final:
raise RuntimeError(
f"Impossible de relire le collaborateur {numero}"
)
logger.info(
f"✅ COLLABORATEUR CRÉÉ: {numero} - "
f"{collaborateur_final['nom']} {collaborateur_final.get('prenom', '')}"
)
return collaborateur_final
except Exception:
if transaction_active:
collab.Read()
for attr in ["No", "CO_No", "Numero"]:
try:
self.cial.CptaApplication.RollbackTrans()
logger.error("❌ Transaction annulée (rollback)")
except Exception:
val = getattr(collab, attr)
if val and isinstance(val, int):
numero_cree = val
break
except:
pass
except:
pass
raise
# Via SQL si pas trouvé
if not numero_cree:
try:
with self._get_sql_connection() as conn:
cursor = conn.cursor()
query = f"SELECT CO_No FROM F_COLLABORATEUR WHERE CO_Nom = '{nom_upper}'"
if prenom:
query += f" AND CO_Prenom = '{prenom}'"
cursor.execute(query)
row = cursor.fetchone()
if row:
numero_cree = row[0]
except Exception as e:
logger.error(f"❌ ERREUR CRÉATION COLLABORATEUR: {e}", exc_info=True)
logger.warning(f"SQL récup numéro: {e}")
logger.info(f"\n{'=' * 70}")
logger.info(
f"✅ COLLABORATEUR CRÉÉ: N°{numero_cree} - {nom_upper} {prenom}"
)
logger.info(f"{'=' * 70}")
# Retourner le collaborateur
if numero_cree:
return self.lire_collaborateur(numero_cree)
else:
return {"nom": nom_upper, "prenom": prenom, "status": "créé"}
except ValueError as e:
logger.warning(f"⚠️ Validation: {e}")
raise
except Exception as e:
logger.error(f"❌ Erreur création collaborateur: {e}", exc_info=True)
raise RuntimeError(f"Échec création collaborateur: {str(e)}")
def modifier_collaborateur(self, numero: int, data: dict) -> dict:
"""Modifie un collaborateur existant via COM Sage"""
"""Modifie un collaborateur existant via COM Sage (BOI)"""
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
logger.info(f"📝 Début modification collaborateur CO_No={numero}")
logger.info(f"\n{'=' * 70}")
logger.info(f"📝 MODIFICATION COLLABORATEUR N°{numero}")
logger.info(f"{'=' * 70}")
try:
with self._com_context(), self._lock_com:
transaction_active = False
try:
# Démarrage transaction
try:
self.cial.CptaApplication.BeginTrans()
transaction_active = True
logger.debug("✓ Transaction Sage démarrée")
except Exception as e:
logger.warning(f"BeginTrans échoué (non critique): {e}")
# ===== LECTURE DU COLLABORATEUR EXISTANT =====
factory_collab = self.cial.FactoryCollaborateur
persist_collab = factory_collab.ReadNumero(numero)
try:
factory = self.cial.FactoryCollaborateur
except AttributeError:
factory = self.cial.CptaApplication.FactoryCollaborateur
if not persist_collab:
# Lire par numéro
try:
persist = factory.ReadNumero(numero)
except Exception as e:
raise ValueError(f"Collaborateur {numero} introuvable: {e}")
if not persist:
raise ValueError(f"Collaborateur {numero} introuvable")
# Cast vers l'interface IBOCollaborateur3
# Cast vers interface
collab = None
for iface in [
"IBOCollaborateur3",
"IBOCollaborateur2",
"IBOCollaborateur",
]:
try:
collab_obj = win32com.client.CastTo(
persist_collab, "IBOCollaborateur3"
)
except Exception:
collab_obj = win32com.client.CastTo(
persist_collab, "IBOCollaborateur"
)
collab = win32com.client.CastTo(persist, iface)
logger.info(f"✓ Cast vers {iface}")
break
except:
pass
if not collab:
collab = persist
collab_obj.Read()
# Charger les données actuelles
try:
collab.Read()
logger.info(f"✓ Collaborateur {numero} chargé")
except Exception as e:
logger.warning(f"Read() ignoré: {e}")
# ===== MAPPING ET MODIFICATION CHAMPS =====
mapping = {
"nom": "CO_Nom",
"prenom": "CO_Prenom",
"fonction": "CO_Fonction",
"adresse": "CO_Adresse",
"complement": "CO_Complement",
"code_postal": "CO_CodePostal",
"ville": "CO_Ville",
"region": "CO_CodeRegion",
"pays": "CO_Pays",
"service": "CO_Service",
"telephone": "CO_Telephone",
"telecopie": "CO_Telecopie",
"email": "CO_EMail",
"tel_portable": "CO_TelPortable",
"matricule": "CO_Matricule",
"facebook": "CO_Facebook",
"linkedin": "CO_LinkedIn",
"skype": "CO_Skype",
"chef_ventes_numero": "CO_NoChefVentes",
}
# ===== HELPER =====
def safe_set(obj, attr, value, max_len=None):
"""Affecte une valeur de manière sécurisée"""
if value is None:
return False
try:
val = str(value) if not isinstance(value, bool) else value
if max_len and isinstance(val, str):
val = val[:max_len]
setattr(obj, attr, val)
logger.debug(f"{attr} = '{val}'")
return True
except Exception as e:
logger.warning(f"{attr}: {e}")
return False
champs_modifies = []
# Champs texte
for py_field, sage_field in mapping.items():
if py_field in data:
try:
valeur = (
str(data[py_field])
if data[py_field] is not None
else ""
)
setattr(collab_obj, sage_field, valeur)
champs_modifies.append(sage_field)
logger.debug(f"{sage_field}: {valeur}")
except Exception as e:
logger.warning(f"{sage_field} non modifié: {e}")
# ===== CHAMPS DIRECTS SUR COLLABORATEUR =====
logger.info("📝 Champs directs...")
# ===== CHAMPS BOOLÉENS =====
bool_mapping = {
"est_vendeur": "CO_Vendeur",
"est_caissier": "CO_Caissier",
"est_acheteur": "CO_Acheteur",
"est_chef_ventes": "CO_ChefVentes",
champs_directs = {
"nom": ("Nom", 35),
"prenom": ("Prenom", 35),
"fonction": ("Fonction", 35),
"service": ("Service", 35),
"matricule": ("Matricule", 10),
"facebook": ("Facebook", 35),
"linkedin": ("LinkedIn", 35),
"skype": ("Skype", 35),
}
for py_field, sage_field in bool_mapping.items():
for py_field, (sage_attr, max_len) in champs_directs.items():
if py_field in data:
try:
valeur = 1 if data[py_field] else 0
setattr(collab_obj, sage_field, valeur)
champs_modifies.append(sage_field)
logger.debug(f"{sage_field}: {valeur}")
except Exception as e:
logger.warning(f"{sage_field} non modifié: {e}")
val = data[py_field]
# Cas spécial: nom en majuscules
if py_field == "nom" and val:
val = str(val).upper().strip()
if safe_set(collab, sage_attr, val, max_len):
champs_modifies.append(sage_attr)
# ===== CO_SOMMEIL (inversé) =====
if "est_actif" in data:
# ===== SOUS-OBJET ADRESSE =====
logger.info("📍 Adresse...")
try:
valeur_sommeil = 0 if data["est_actif"] else 1
collab_obj.CO_Sommeil = valeur_sommeil
champs_modifies.append("CO_Sommeil")
logger.debug(f" ✓ CO_Sommeil: {valeur_sommeil}")
except Exception as e:
logger.warning(f" ⚠ CO_Sommeil non modifié: {e}")
adresse_obj = collab.Adresse
champs_adresse = {
"adresse": ("Adresse", 35),
"complement": ("Complement", 35),
"code_postal": ("CodePostal", 9),
"ville": ("Ville", 35),
"code_region": ("CodeRegion", 25),
"pays": ("Pays", 35),
}
for py_field, (sage_attr, max_len) in champs_adresse.items():
if py_field in data:
if safe_set(
adresse_obj, sage_attr, data[py_field], max_len
):
champs_modifies.append(f"Adresse.{sage_attr}")
except Exception as e:
logger.warning(f"⚠️ Erreur accès Adresse: {e}")
# ===== SOUS-OBJET TELECOM =====
logger.info("📞 Telecom...")
try:
telecom_obj = collab.Telecom
champs_telecom = {
"telephone": ("Telephone", 21),
"telecopie": ("Telecopie", 21),
"email": ("EMail", 69),
"tel_portable": ("Portable", 21),
}
for py_field, (sage_attr, max_len) in champs_telecom.items():
if py_field in data:
if safe_set(
telecom_obj, sage_attr, data[py_field], max_len
):
champs_modifies.append(f"Telecom.{sage_attr}")
except Exception as e:
logger.warning(f"⚠️ Erreur accès Telecom: {e}")
# ===== CHAMPS BOOLÉENS =====
logger.info("🔘 Booléens...")
champs_bool = {
"vendeur": "Vendeur",
"caissier": "Caissier",
"acheteur": "Acheteur",
"sommeil": "Sommeil",
"chef_ventes": "ChefVentes",
}
for py_field, sage_attr in champs_bool.items():
if py_field in data and data[py_field] is not None:
try:
val = bool(data[py_field])
setattr(collab, sage_attr, val)
champs_modifies.append(sage_attr)
logger.debug(f"{sage_attr} = {val}")
except Exception as e:
logger.warning(f"{sage_attr}: {e}")
# ===== VÉRIFICATION =====
if not champs_modifies:
logger.info(" Aucun champ à modifier")
logger.info(" Aucun champ à modifier")
return self.lire_collaborateur(numero)
logger.info(
f"📋 {len(champs_modifies)} champ(s) à modifier: {champs_modifies}"
)
# ===== WRITE =====
collab_obj.Write()
logger.info(
f"💾 Collaborateur modifié ({len(champs_modifies)} champs)"
)
# Commit transaction
if transaction_active:
logger.info("💾 Write()...")
try:
self.cial.CptaApplication.CommitTrans()
logger.info("✓ Transaction committée")
except Exception:
pass
# ===== RELECTURE POUR RETOUR COMPLET =====
collaborateur_final = self.lire_collaborateur(numero)
if not collaborateur_final:
raise RuntimeError(
f"Impossible de relire le collaborateur {numero}"
)
logger.info(
f"✅ COLLABORATEUR MODIFIÉ: {numero} - "
f"{collaborateur_final['nom']} {collaborateur_final.get('prenom', '')}"
)
return collaborateur_final
except Exception:
if transaction_active:
try:
self.cial.CptaApplication.RollbackTrans()
logger.error("❌ Transaction annulée (rollback)")
except Exception:
pass
raise
collab.Write()
logger.info("✅ Write() RÉUSSI!")
except Exception as e:
logger.error(
f"❌ ERREUR MODIFICATION COLLABORATEUR {numero}: {e}", exc_info=True
)
logger.error(f"❌ Write() échoué: {e}")
raise RuntimeError(f"Échec Write(): {e}")
# ===== RETOUR =====
logger.info(f"\n{'=' * 70}")
logger.info(f"✅ COLLABORATEUR MODIFIÉ: N°{numero}")
logger.info(f"{'=' * 70}")
return self.lire_collaborateur(numero)
except ValueError as e:
logger.warning(f"⚠️ Validation: {e}")
raise
except Exception as e:
logger.error(f"❌ Erreur modification collaborateur: {e}", exc_info=True)
raise RuntimeError(f"Échec modification collaborateur: {str(e)}")