Push simple
This commit is contained in:
parent
be7bc287c0
commit
7ca64e2ea6
2 changed files with 972 additions and 97 deletions
683
main.py
683
main.py
|
|
@ -2535,19 +2535,69 @@ def prospect_get(req: CodeRequest):
|
||||||
# ENDPOINTS - FOURNISSEURS
|
# ENDPOINTS - FOURNISSEURS
|
||||||
# =====================================================
|
# =====================================================
|
||||||
@app.post("/sage/fournisseurs/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/fournisseurs/list", dependencies=[Depends(verify_token)])
|
||||||
def fournisseurs_list(req: FiltreRequest):
|
def fournisseurs_list_direct(req: FiltreRequest):
|
||||||
"""📋 Liste tous les fournisseurs (CT_Type=1)"""
|
"""
|
||||||
|
⚡ ENDPOINT DIRECT : Liste fournisseurs SANS passer par le cache
|
||||||
|
|
||||||
|
Lecture directe depuis FactoryFournisseur - toujours à jour
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
fournisseurs = sage.lister_tous_fournisseurs(req.filtre)
|
if not sage or not sage.cial:
|
||||||
return {"success": True, "data": fournisseurs}
|
raise HTTPException(503, "Service Sage indisponible")
|
||||||
|
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
factory = sage.cial.CptaApplication.FactoryFournisseur
|
||||||
|
fournisseurs = []
|
||||||
|
index = 1
|
||||||
|
max_iterations = 10000
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
max_erreurs = 50
|
||||||
|
|
||||||
|
filtre_lower = req.filtre.lower() if req.filtre else ""
|
||||||
|
|
||||||
|
logger.info(f"🔍 Lecture directe fournisseurs (filtre='{req.filtre}')")
|
||||||
|
|
||||||
|
while index < max_iterations and erreurs_consecutives < max_erreurs:
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
obj = sage._cast_client(persist)
|
||||||
|
if obj:
|
||||||
|
data = sage._extraire_client(obj)
|
||||||
|
|
||||||
|
# Appliquer le filtre si nécessaire
|
||||||
|
if not filtre_lower or \
|
||||||
|
filtre_lower in data["numero"].lower() or \
|
||||||
|
filtre_lower in data["intitule"].lower():
|
||||||
|
fournisseurs.append(data)
|
||||||
|
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
erreurs_consecutives += 1
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
if erreurs_consecutives >= max_erreurs:
|
||||||
|
logger.warning(f"⚠️ Arrêt après {max_erreurs} erreurs consécutives")
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(fournisseurs)} fournisseurs retournés (lecture directe)")
|
||||||
|
return {"success": True, "data": fournisseurs}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur liste fournisseurs: {e}")
|
logger.error(f"❌ Erreur lecture directe fournisseurs: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/fournisseurs/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/fournisseurs/get", dependencies=[Depends(verify_token)])
|
||||||
def fournisseur_get(req: CodeRequest):
|
def fournisseur_get(req: CodeRequest):
|
||||||
"""📄 Lecture d'un fournisseur par code"""
|
"""
|
||||||
|
✅ NOUVEAU : Lecture d'un fournisseur par code
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
fournisseur = sage.lire_fournisseur(req.code)
|
fournisseur = sage.lire_fournisseur(req.code)
|
||||||
if not fournisseur:
|
if not fournisseur:
|
||||||
|
|
@ -2981,6 +3031,627 @@ def diagnostiquer_longueurs_champs():
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# À ajouter dans main.py (Windows Gateway)
|
||||||
|
|
||||||
|
@app.get("/sage/diagnostic/fournisseurs-analyse-complete", dependencies=[Depends(verify_token)])
|
||||||
|
def analyser_fournisseurs_complet():
|
||||||
|
"""
|
||||||
|
🔍 DIAGNOSTIC ULTRA-COMPLET : Découverte fournisseurs
|
||||||
|
|
||||||
|
Teste TOUTES les méthodes possibles pour identifier les fournisseurs :
|
||||||
|
1. CT_Type = 1 (méthode classique)
|
||||||
|
2. CT_Qualite = 2 ou 3 (méthode moderne)
|
||||||
|
3. FactoryFournisseur (méthode directe)
|
||||||
|
4. CT_TypeTiers
|
||||||
|
5. Analyse des champs disponibles
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not sage or not sage.cial:
|
||||||
|
raise HTTPException(503, "Service Sage indisponible")
|
||||||
|
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
|
||||||
|
diagnostic = {
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"methodes_testees": [],
|
||||||
|
"fournisseurs_detectes": {},
|
||||||
|
"tiers_analyses": [],
|
||||||
|
"champs_disponibles": {},
|
||||||
|
"recommendation": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# MÉTHODE 1 : FactoryFournisseur (prioritaire)
|
||||||
|
# =============================================
|
||||||
|
logger.info("[DIAG] Test FactoryFournisseur...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
factory_fourn = sage.cial.CptaApplication.FactoryFournisseur
|
||||||
|
fournisseurs_factory = []
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
while index <= 100: # Scanner 100 premiers
|
||||||
|
try:
|
||||||
|
persist = factory_fourn.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
fourn = sage._cast_client(persist)
|
||||||
|
if fourn:
|
||||||
|
fournisseurs_factory.append({
|
||||||
|
"numero": getattr(fourn, "CT_Num", ""),
|
||||||
|
"intitule": getattr(fourn, "CT_Intitule", ""),
|
||||||
|
"ct_type": getattr(fourn, "CT_Type", None),
|
||||||
|
"ct_qualite": getattr(fourn, "CT_Qualite", None),
|
||||||
|
"methode": "FactoryFournisseur"
|
||||||
|
})
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Erreur index {index}: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
diagnostic["methodes_testees"].append({
|
||||||
|
"methode": "FactoryFournisseur",
|
||||||
|
"succes": True,
|
||||||
|
"nb_fournisseurs": len(fournisseurs_factory),
|
||||||
|
"disponible": True
|
||||||
|
})
|
||||||
|
|
||||||
|
if fournisseurs_factory:
|
||||||
|
diagnostic["fournisseurs_detectes"]["factory"] = fournisseurs_factory[:10]
|
||||||
|
logger.info(f"✅ {len(fournisseurs_factory)} fournisseurs via FactoryFournisseur")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
diagnostic["methodes_testees"].append({
|
||||||
|
"methode": "FactoryFournisseur",
|
||||||
|
"succes": False,
|
||||||
|
"erreur": str(e),
|
||||||
|
"disponible": False
|
||||||
|
})
|
||||||
|
logger.info(f"❌ FactoryFournisseur indisponible: {e}")
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# MÉTHODE 2 : Analyse FactoryClient avec tous les champs
|
||||||
|
# =============================================
|
||||||
|
logger.info("[DIAG] Analyse complète FactoryClient...")
|
||||||
|
|
||||||
|
factory_client = sage.cial.CptaApplication.FactoryClient
|
||||||
|
|
||||||
|
fournisseurs_ct_type = []
|
||||||
|
fournisseurs_ct_qualite = []
|
||||||
|
tous_tiers = []
|
||||||
|
champs_vus = set()
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
while index <= 100: # Scanner 100 premiers tiers
|
||||||
|
try:
|
||||||
|
persist = factory_client.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
tiers = sage._cast_client(persist)
|
||||||
|
if not tiers:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extraction COMPLÈTE de tous les champs
|
||||||
|
tiers_info = {
|
||||||
|
"index": index,
|
||||||
|
"numero": getattr(tiers, "CT_Num", ""),
|
||||||
|
"intitule": getattr(tiers, "CT_Intitule", ""),
|
||||||
|
"champs": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scanner TOUS les attributs CT_*
|
||||||
|
for attr in dir(tiers):
|
||||||
|
if attr.startswith("CT_") and not callable(getattr(tiers, attr, None)):
|
||||||
|
try:
|
||||||
|
valeur = getattr(tiers, attr, None)
|
||||||
|
if valeur is not None:
|
||||||
|
tiers_info["champs"][attr] = {
|
||||||
|
"valeur": str(valeur),
|
||||||
|
"type": type(valeur).__name__
|
||||||
|
}
|
||||||
|
champs_vus.add(attr)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Analyses spécifiques
|
||||||
|
ct_type = getattr(tiers, "CT_Type", None)
|
||||||
|
ct_qualite = getattr(tiers, "CT_Qualite", None)
|
||||||
|
ct_prospect = getattr(tiers, "CT_Prospect", None)
|
||||||
|
|
||||||
|
tiers_info["ct_type"] = ct_type
|
||||||
|
tiers_info["ct_qualite"] = ct_qualite
|
||||||
|
tiers_info["ct_prospect"] = ct_prospect
|
||||||
|
|
||||||
|
# Identifier si c'est un fournisseur selon différentes méthodes
|
||||||
|
tiers_info["analyses"] = {}
|
||||||
|
|
||||||
|
# Test CT_Type = 1
|
||||||
|
if ct_type == 1:
|
||||||
|
tiers_info["analyses"]["ct_type_1"] = True
|
||||||
|
fournisseurs_ct_type.append(tiers_info)
|
||||||
|
|
||||||
|
# Test CT_Qualite
|
||||||
|
if ct_qualite is not None:
|
||||||
|
tiers_info["ct_qualite_libelle"] = {
|
||||||
|
0: "Aucune",
|
||||||
|
1: "Client uniquement",
|
||||||
|
2: "Fournisseur uniquement",
|
||||||
|
3: "Client ET Fournisseur"
|
||||||
|
}.get(ct_qualite, f"Inconnu ({ct_qualite})")
|
||||||
|
|
||||||
|
if ct_qualite in [2, 3]:
|
||||||
|
tiers_info["analyses"]["ct_qualite_2_3"] = True
|
||||||
|
fournisseurs_ct_qualite.append(tiers_info)
|
||||||
|
|
||||||
|
# Test via FactoryFournisseur.ReadNumero
|
||||||
|
try:
|
||||||
|
factory_fourn = sage.cial.CptaApplication.FactoryFournisseur
|
||||||
|
persist_test = factory_fourn.ReadNumero(tiers_info["numero"])
|
||||||
|
if persist_test:
|
||||||
|
tiers_info["analyses"]["factory_fournisseur_accessible"] = True
|
||||||
|
except:
|
||||||
|
tiers_info["analyses"]["factory_fournisseur_accessible"] = False
|
||||||
|
|
||||||
|
tous_tiers.append(tiers_info)
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Erreur tiers {index}: {e}")
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
diagnostic["tiers_analyses"] = tous_tiers[:20] # Garder 20 exemples
|
||||||
|
diagnostic["champs_disponibles"] = sorted(list(champs_vus))
|
||||||
|
|
||||||
|
# Statistiques
|
||||||
|
diagnostic["statistiques"] = {
|
||||||
|
"total_tiers_scannes": len(tous_tiers),
|
||||||
|
"fournisseurs_ct_type_1": len(fournisseurs_ct_type),
|
||||||
|
"fournisseurs_ct_qualite_2_3": len(fournisseurs_ct_qualite),
|
||||||
|
"champs_ct_detectes": len(champs_vus)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exemples de fournisseurs détectés
|
||||||
|
if fournisseurs_ct_type:
|
||||||
|
diagnostic["fournisseurs_detectes"]["ct_type"] = [
|
||||||
|
{
|
||||||
|
"numero": t["numero"],
|
||||||
|
"intitule": t["intitule"],
|
||||||
|
"ct_type": t["ct_type"]
|
||||||
|
}
|
||||||
|
for t in fournisseurs_ct_type[:10]
|
||||||
|
]
|
||||||
|
|
||||||
|
if fournisseurs_ct_qualite:
|
||||||
|
diagnostic["fournisseurs_detectes"]["ct_qualite"] = [
|
||||||
|
{
|
||||||
|
"numero": t["numero"],
|
||||||
|
"intitule": t["intitule"],
|
||||||
|
"ct_qualite": t["ct_qualite"],
|
||||||
|
"libelle": t["ct_qualite_libelle"]
|
||||||
|
}
|
||||||
|
for t in fournisseurs_ct_qualite[:10]
|
||||||
|
]
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# MÉTHODE 3 : Analyse des champs disponibles
|
||||||
|
# =============================================
|
||||||
|
logger.info("[DIAG] Analyse des champs disponibles...")
|
||||||
|
|
||||||
|
# Prendre un tiers exemple et lister TOUS ses attributs
|
||||||
|
if tous_tiers:
|
||||||
|
tiers_exemple = tous_tiers[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
persist_ex = factory_client.ReadNumero(tiers_exemple["numero"])
|
||||||
|
if persist_ex:
|
||||||
|
obj_ex = sage._cast_client(persist_ex)
|
||||||
|
|
||||||
|
tous_attributs = {}
|
||||||
|
for attr in dir(obj_ex):
|
||||||
|
if not attr.startswith("_") and not callable(getattr(obj_ex, attr, None)):
|
||||||
|
try:
|
||||||
|
val = getattr(obj_ex, attr, None)
|
||||||
|
if val is not None:
|
||||||
|
tous_attributs[attr] = {
|
||||||
|
"type": type(val).__name__,
|
||||||
|
"valeur_exemple": str(val)[:50]
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
diagnostic["attributs_complets_exemple"] = tous_attributs
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
diagnostic["erreur_analyse_attributs"] = str(e)
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# RECOMMANDATION FINALE
|
||||||
|
# =============================================
|
||||||
|
logger.info("[DIAG] Génération recommandation...")
|
||||||
|
|
||||||
|
if "factory" in diagnostic["fournisseurs_detectes"]:
|
||||||
|
diagnostic["recommendation"] = {
|
||||||
|
"methode": "FactoryFournisseur",
|
||||||
|
"code_exemple": """
|
||||||
|
# Utiliser FactoryFournisseur directement
|
||||||
|
factory_fourn = sage.cial.CptaApplication.FactoryFournisseur
|
||||||
|
persist = factory_fourn.List(index) # ou .ReadNumero(code)
|
||||||
|
""",
|
||||||
|
"implementation": "Modifier lister_tous_fournisseurs() pour utiliser FactoryFournisseur au lieu de FactoryClient",
|
||||||
|
"priorite": "HAUTE - Méthode la plus fiable"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif fournisseurs_ct_qualite:
|
||||||
|
diagnostic["recommendation"] = {
|
||||||
|
"methode": "CT_Qualite",
|
||||||
|
"condition": "CT_Qualite IN (2, 3)",
|
||||||
|
"code_exemple": """
|
||||||
|
# Filtrer sur CT_Qualite
|
||||||
|
qualite = getattr(tiers, "CT_Qualite", None)
|
||||||
|
if qualite in [2, 3]: # 2=Fournisseur, 3=Client+Fournisseur
|
||||||
|
# C'est un fournisseur
|
||||||
|
""",
|
||||||
|
"implementation": "Modifier _extraire_client() et le cache pour utiliser CT_Qualite",
|
||||||
|
"priorite": "MOYENNE - Méthode moderne"
|
||||||
|
}
|
||||||
|
|
||||||
|
elif fournisseurs_ct_type:
|
||||||
|
diagnostic["recommendation"] = {
|
||||||
|
"methode": "CT_Type",
|
||||||
|
"condition": "CT_Type = 1",
|
||||||
|
"code_exemple": """
|
||||||
|
# Filtrer sur CT_Type (ancienne méthode)
|
||||||
|
type_tiers = getattr(tiers, "CT_Type", 0)
|
||||||
|
if type_tiers == 1: # 1=Fournisseur
|
||||||
|
# C'est un fournisseur
|
||||||
|
""",
|
||||||
|
"implementation": "La méthode actuelle devrait fonctionner",
|
||||||
|
"priorite": "BASSE - Méthode classique"
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
|
diagnostic["recommendation"] = {
|
||||||
|
"methode": "AUCUNE",
|
||||||
|
"message": "Aucune méthode n'a permis d'identifier des fournisseurs",
|
||||||
|
"actions": [
|
||||||
|
"Vérifier si des fournisseurs existent dans Sage",
|
||||||
|
"Augmenter le nombre de tiers scannés (actuellement 100)",
|
||||||
|
"Vérifier les permissions de l'utilisateur Sage",
|
||||||
|
"Consulter la documentation Sage pour votre version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# CODE DE CORRECTION SUGGÉRÉ
|
||||||
|
# =============================================
|
||||||
|
if "factory" in diagnostic["fournisseurs_detectes"]:
|
||||||
|
diagnostic["code_correction"] = {
|
||||||
|
"fichier": "sage_connector.py",
|
||||||
|
"fonction": "lister_tous_fournisseurs",
|
||||||
|
"code": """
|
||||||
|
def lister_tous_fournisseurs(self, filtre=""):
|
||||||
|
'''Liste tous les fournisseurs via FactoryFournisseur'''
|
||||||
|
if not self.cial:
|
||||||
|
return []
|
||||||
|
|
||||||
|
fournisseurs = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._com_context(), self._lock_com:
|
||||||
|
factory = self.cial.CptaApplication.FactoryFournisseur # ✅ CORRECTION
|
||||||
|
index = 1
|
||||||
|
max_iterations = 10000
|
||||||
|
|
||||||
|
while index < max_iterations:
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
obj = self._cast_client(persist)
|
||||||
|
if obj:
|
||||||
|
data = self._extraire_client(obj)
|
||||||
|
|
||||||
|
# Filtrer si nécessaire
|
||||||
|
if not filtre or \\
|
||||||
|
filtre.lower() in data["numero"].lower() or \\
|
||||||
|
filtre.lower() in data["intitule"].lower():
|
||||||
|
fournisseurs.append(data)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
except:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(fournisseurs)} fournisseurs retournés")
|
||||||
|
return fournisseurs
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur liste fournisseurs: {e}")
|
||||||
|
return []
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
elif fournisseurs_ct_qualite:
|
||||||
|
diagnostic["code_correction"] = {
|
||||||
|
"fichier": "sage_connector.py",
|
||||||
|
"fonction": "_extraire_client + _refresh_cache_clients",
|
||||||
|
"code": """
|
||||||
|
# Modifier _extraire_client pour inclure CT_Qualite
|
||||||
|
def _extraire_client(self, client_obj):
|
||||||
|
data = {
|
||||||
|
"numero": getattr(client_obj, "CT_Num", ""),
|
||||||
|
"intitule": getattr(client_obj, "CT_Intitule", ""),
|
||||||
|
"type": getattr(client_obj, "CT_Type", 0),
|
||||||
|
"qualite": getattr(client_obj, "CT_Qualite", 0), # ✅ AJOUT
|
||||||
|
"est_prospect": getattr(client_obj, "CT_Prospect", 0) == 1,
|
||||||
|
"est_fournisseur": getattr(client_obj, "CT_Qualite", 0) in [2, 3] # ✅ AJOUT
|
||||||
|
}
|
||||||
|
# ... reste du code
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Modifier lister_tous_fournisseurs
|
||||||
|
def lister_tous_fournisseurs(self, filtre=""):
|
||||||
|
with self._lock_clients:
|
||||||
|
if not filtre:
|
||||||
|
return [c for c in self._cache_clients if c.get("est_fournisseur")] # ✅ MODIFICATION
|
||||||
|
|
||||||
|
filtre_lower = filtre.lower()
|
||||||
|
return [
|
||||||
|
c for c in self._cache_clients
|
||||||
|
if c.get("est_fournisseur") and # ✅ MODIFICATION
|
||||||
|
(filtre_lower in c["numero"].lower() or
|
||||||
|
filtre_lower in c["intitule"].lower())
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("[DIAG] ✅ Analyse complète terminée")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"diagnostic": diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[DIAG] ❌ Erreur: {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/sage/diagnostic/fournisseurs-rapide", dependencies=[Depends(verify_token)])
|
||||||
|
def test_fournisseurs_rapide():
|
||||||
|
"""
|
||||||
|
⚡ DIAGNOSTIC RAPIDE : Test des 3 méthodes principales
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not sage or not sage.cial:
|
||||||
|
raise HTTPException(503, "Service Sage indisponible")
|
||||||
|
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
resultats = {}
|
||||||
|
|
||||||
|
# Test 1 : FactoryFournisseur
|
||||||
|
try:
|
||||||
|
factory = sage.cial.CptaApplication.FactoryFournisseur
|
||||||
|
count = 0
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
while index <= 10:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
count += 1
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
resultats["FactoryFournisseur"] = {
|
||||||
|
"disponible": True,
|
||||||
|
"nb_trouves": count,
|
||||||
|
"statut": "✅ FONCTIONNEL" if count > 0 else "⚠️ Aucun fournisseur"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
resultats["FactoryFournisseur"] = {
|
||||||
|
"disponible": False,
|
||||||
|
"erreur": str(e),
|
||||||
|
"statut": "❌ INDISPONIBLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test 2 : CT_Qualite
|
||||||
|
factory_client = sage.cial.CptaApplication.FactoryClient
|
||||||
|
count_qualite = 0
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
while index <= 50:
|
||||||
|
try:
|
||||||
|
persist = factory_client.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
tiers = sage._cast_client(persist)
|
||||||
|
if tiers:
|
||||||
|
qualite = getattr(tiers, "CT_Qualite", None)
|
||||||
|
if qualite in [2, 3]:
|
||||||
|
count_qualite += 1
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
resultats["CT_Qualite"] = {
|
||||||
|
"nb_trouves": count_qualite,
|
||||||
|
"statut": "✅ FONCTIONNEL" if count_qualite > 0 else "⚠️ Aucun fournisseur"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test 3 : CT_Type
|
||||||
|
count_type = 0
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
while index <= 50:
|
||||||
|
try:
|
||||||
|
persist = factory_client.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
tiers = sage._cast_client(persist)
|
||||||
|
if tiers:
|
||||||
|
type_tiers = getattr(tiers, "CT_Type", 0)
|
||||||
|
if type_tiers == 1:
|
||||||
|
count_type += 1
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
resultats["CT_Type"] = {
|
||||||
|
"nb_trouves": count_type,
|
||||||
|
"statut": "✅ FONCTIONNEL" if count_type > 0 else "⚠️ Aucun fournisseur"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Recommandation
|
||||||
|
if resultats["FactoryFournisseur"].get("nb_trouves", 0) > 0:
|
||||||
|
recommandation = "Utiliser FactoryFournisseur (méthode la plus fiable)"
|
||||||
|
elif count_qualite > 0:
|
||||||
|
recommandation = "Utiliser CT_Qualite (méthode moderne)"
|
||||||
|
elif count_type > 0:
|
||||||
|
recommandation = "Utiliser CT_Type (méthode classique)"
|
||||||
|
else:
|
||||||
|
recommandation = "⚠️ AUCUNE MÉTHODE - Vérifier si des fournisseurs existent dans Sage"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"resultats": resultats,
|
||||||
|
"recommandation": recommandation,
|
||||||
|
"actions_suivantes": [
|
||||||
|
"Appeler /sage/diagnostic/fournisseurs-analyse-complete pour plus de détails",
|
||||||
|
"Vérifier dans Sage si des fournisseurs existent",
|
||||||
|
"Appliquer la correction suggérée selon la méthode fonctionnelle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[DIAG] Erreur: {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/sage/debug/fournisseurs-direct", dependencies=[Depends(verify_token)])
|
||||||
|
def debug_fournisseurs_direct():
|
||||||
|
"""
|
||||||
|
🔍 Test direct : Lecture des fournisseurs SANS passer par le cache
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not sage or not sage.cial:
|
||||||
|
raise HTTPException(503, "Service Sage indisponible")
|
||||||
|
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
factory = sage.cial.CptaApplication.FactoryFournisseur
|
||||||
|
|
||||||
|
fournisseurs_direct = []
|
||||||
|
index = 1
|
||||||
|
|
||||||
|
# Lire les 10 premiers directement
|
||||||
|
while index <= 10:
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
obj = sage._cast_client(persist)
|
||||||
|
if obj:
|
||||||
|
fournisseurs_direct.append({
|
||||||
|
"numero": getattr(obj, "CT_Num", ""),
|
||||||
|
"intitule": getattr(obj, "CT_Intitule", ""),
|
||||||
|
"type": getattr(obj, "CT_Type", -1)
|
||||||
|
})
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Erreur index {index}: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Vérifier l'état du cache
|
||||||
|
cache_existe = hasattr(sage, '_cache_fournisseurs')
|
||||||
|
cache_count = len(sage._cache_fournisseurs) if cache_existe else 0
|
||||||
|
cache_last_update = (
|
||||||
|
sage._cache_fournisseurs_last_update.isoformat()
|
||||||
|
if cache_existe and sage._cache_fournisseurs_last_update
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"lecture_directe": {
|
||||||
|
"nb_fournisseurs": len(fournisseurs_direct),
|
||||||
|
"exemples": fournisseurs_direct
|
||||||
|
},
|
||||||
|
"etat_cache": {
|
||||||
|
"cache_existe": cache_existe,
|
||||||
|
"cache_count": cache_count,
|
||||||
|
"last_update": cache_last_update,
|
||||||
|
"attributs_sage_connector": [
|
||||||
|
attr for attr in dir(sage)
|
||||||
|
if 'fournisseur' in attr.lower()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"diagnostic": {
|
||||||
|
"factory_fournisseur_ok": len(fournisseurs_direct) > 0,
|
||||||
|
"cache_initialise": cache_existe and cache_count > 0,
|
||||||
|
"probleme": (
|
||||||
|
"Cache non initialisé - appeler sage.connecter() ou _refresh_cache_fournisseurs()"
|
||||||
|
if not cache_existe or cache_count == 0
|
||||||
|
else "Tout est OK"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur debug direct: {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/debug/fournisseurs-init-cache", dependencies=[Depends(verify_token)])
|
||||||
|
def init_cache_fournisseurs_force():
|
||||||
|
"""
|
||||||
|
🔧 Force l'initialisation du cache fournisseurs (si la méthode existe)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not sage:
|
||||||
|
raise HTTPException(503, "Service Sage indisponible")
|
||||||
|
|
||||||
|
# Vérifier que la méthode existe
|
||||||
|
if not hasattr(sage, '_refresh_cache_fournisseurs'):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"erreur": "La méthode _refresh_cache_fournisseurs() n'existe pas dans sage_connector.py",
|
||||||
|
"solution": "Vérifier que le code a bien été appliqué et redémarrer main.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Appeler la méthode
|
||||||
|
logger.info("🔄 Initialisation forcée du cache fournisseurs...")
|
||||||
|
sage._refresh_cache_fournisseurs()
|
||||||
|
|
||||||
|
# Vérifier le résultat
|
||||||
|
cache_count = len(sage._cache_fournisseurs) if hasattr(sage, '_cache_fournisseurs') else 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"cache_initialise": cache_count > 0,
|
||||||
|
"nb_fournisseurs": cache_count,
|
||||||
|
"exemples": sage._cache_fournisseurs[:3] if cache_count > 0 else [],
|
||||||
|
"message": (
|
||||||
|
f"✅ Cache initialisé : {cache_count} fournisseurs"
|
||||||
|
if cache_count > 0
|
||||||
|
else "❌ Échec : cache toujours vide après refresh"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur init cache: {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# LANCEMENT
|
# LANCEMENT
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,18 @@ class SageConnector:
|
||||||
self.mot_de_passe = mot_de_passe
|
self.mot_de_passe = mot_de_passe
|
||||||
self.cial = None
|
self.cial = None
|
||||||
|
|
||||||
# Cache
|
# Caches existants
|
||||||
self._cache_clients: List[Dict] = []
|
self._cache_clients: List[Dict] = []
|
||||||
self._cache_articles: List[Dict] = []
|
self._cache_articles: List[Dict] = []
|
||||||
self._cache_clients_dict: Dict[str, Dict] = {}
|
self._cache_clients_dict: Dict[str, Dict] = {}
|
||||||
self._cache_articles_dict: Dict[str, Dict] = {}
|
self._cache_articles_dict: Dict[str, Dict] = {}
|
||||||
|
|
||||||
# Métadonnées cache
|
# ✅ NOUVEAU : Cache fournisseurs dédié
|
||||||
|
self._cache_fournisseurs: List[Dict] = []
|
||||||
|
self._cache_fournisseurs_dict: Dict[str, Dict] = {}
|
||||||
|
self._cache_fournisseurs_last_update: Optional[datetime] = None
|
||||||
|
|
||||||
|
# Métadonnées cache existantes
|
||||||
self._cache_clients_last_update: Optional[datetime] = None
|
self._cache_clients_last_update: Optional[datetime] = None
|
||||||
self._cache_articles_last_update: Optional[datetime] = None
|
self._cache_articles_last_update: Optional[datetime] = None
|
||||||
self._cache_ttl_minutes = 15
|
self._cache_ttl_minutes = 15
|
||||||
|
|
@ -44,12 +49,12 @@ class SageConnector:
|
||||||
self._refresh_thread: Optional[threading.Thread] = None
|
self._refresh_thread: Optional[threading.Thread] = None
|
||||||
self._stop_refresh = threading.Event()
|
self._stop_refresh = threading.Event()
|
||||||
|
|
||||||
# Locks thread-safe
|
# Locks
|
||||||
self._lock_clients = threading.RLock()
|
self._lock_clients = threading.RLock()
|
||||||
self._lock_articles = threading.RLock()
|
self._lock_articles = threading.RLock()
|
||||||
self._lock_com = threading.RLock() # Lock pour accès COM
|
self._lock_com = threading.RLock()
|
||||||
|
|
||||||
# Thread-local storage pour COM
|
# Thread-local storage pour COM
|
||||||
self._thread_local = threading.local()
|
self._thread_local = threading.local()
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
@ -102,22 +107,24 @@ class SageConnector:
|
||||||
"""Connexion initiale à Sage"""
|
"""Connexion initiale à Sage"""
|
||||||
try:
|
try:
|
||||||
with self._com_context():
|
with self._com_context():
|
||||||
self.cial = win32com.client.gencache.EnsureDispatch(
|
self.cial = win32com.client.gencache.EnsureDispatch("Objets100c.Cial.Stream")
|
||||||
"Objets100c.Cial.Stream"
|
|
||||||
)
|
|
||||||
self.cial.Name = self.chemin_base
|
self.cial.Name = self.chemin_base
|
||||||
self.cial.Loggable.UserName = self.utilisateur
|
self.cial.Loggable.UserName = self.utilisateur
|
||||||
self.cial.Loggable.UserPwd = self.mot_de_passe
|
self.cial.Loggable.UserPwd = self.mot_de_passe
|
||||||
self.cial.Open()
|
self.cial.Open()
|
||||||
|
|
||||||
logger.info(f"Connexion Sage réussie: {self.chemin_base}")
|
logger.info(f"✅ Connexion Sage réussie: {self.chemin_base}")
|
||||||
|
|
||||||
# Chargement initial du cache
|
# Chargement initial du cache
|
||||||
logger.info("Chargement initial du cache...")
|
logger.info("📦 Chargement initial du cache...")
|
||||||
self._refresh_cache_clients()
|
self._refresh_cache_clients()
|
||||||
self._refresh_cache_articles()
|
self._refresh_cache_articles()
|
||||||
|
self._refresh_cache_fournisseurs() # ✅ CETTE LIGNE DOIT ÊTRE LÀ
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Cache initialisé: {len(self._cache_clients)} clients, {len(self._cache_articles)} articles"
|
f"✅ Cache initialisé: {len(self._cache_clients)} clients, "
|
||||||
|
f"{len(self._cache_articles)} articles, "
|
||||||
|
f"{len(self._cache_fournisseurs)} fournisseurs" # ✅ AJOUT
|
||||||
)
|
)
|
||||||
|
|
||||||
# Démarrage du thread d'actualisation
|
# Démarrage du thread d'actualisation
|
||||||
|
|
@ -126,7 +133,7 @@ class SageConnector:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur connexion Sage: {e}", exc_info=True)
|
logger.error(f"❌ Erreur connexion Sage: {e}", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def deconnecter(self):
|
def deconnecter(self):
|
||||||
|
|
@ -152,32 +159,32 @@ class SageConnector:
|
||||||
"""Démarre le thread d'actualisation automatique"""
|
"""Démarre le thread d'actualisation automatique"""
|
||||||
|
|
||||||
def refresh_loop():
|
def refresh_loop():
|
||||||
# Initialiser COM pour ce thread worker
|
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while not self._stop_refresh.is_set():
|
while not self._stop_refresh.is_set():
|
||||||
time.sleep(60) # Vérifier toutes les minutes
|
time.sleep(60)
|
||||||
|
|
||||||
# Clients
|
# Clients
|
||||||
if self._cache_clients_last_update:
|
if self._cache_clients_last_update:
|
||||||
age = datetime.now() - self._cache_clients_last_update
|
age = datetime.now() - self._cache_clients_last_update
|
||||||
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
||||||
logger.info(
|
|
||||||
f"Actualisation cache clients (âge: {age.seconds//60}min)"
|
|
||||||
)
|
|
||||||
self._refresh_cache_clients()
|
self._refresh_cache_clients()
|
||||||
|
|
||||||
# Articles
|
# Articles
|
||||||
if self._cache_articles_last_update:
|
if self._cache_articles_last_update:
|
||||||
age = datetime.now() - self._cache_articles_last_update
|
age = datetime.now() - self._cache_articles_last_update
|
||||||
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
||||||
logger.info(
|
|
||||||
f"Actualisation cache articles (âge: {age.seconds//60}min)"
|
|
||||||
)
|
|
||||||
self._refresh_cache_articles()
|
self._refresh_cache_articles()
|
||||||
|
|
||||||
|
# ✅ AJOUT : Fournisseurs
|
||||||
|
if hasattr(self, '_cache_fournisseurs_last_update') and self._cache_fournisseurs_last_update:
|
||||||
|
age = datetime.now() - self._cache_fournisseurs_last_update
|
||||||
|
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
||||||
|
logger.info(f"🔄 Actualisation cache fournisseurs (âge: {age.seconds//60}min)")
|
||||||
|
self._refresh_cache_fournisseurs()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Nettoyer COM en fin de thread
|
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
self._refresh_thread = threading.Thread(
|
self._refresh_thread = threading.Thread(
|
||||||
|
|
@ -298,6 +305,169 @@ class SageConnector:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f" Erreur refresh articles: {e}", exc_info=True)
|
logger.error(f" Erreur refresh articles: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _refresh_cache_fournisseurs(self):
|
||||||
|
"""
|
||||||
|
✅ CORRIGÉ FINAL : Actualise le cache des fournisseurs via FactoryFournisseur
|
||||||
|
"""
|
||||||
|
if not self.cial:
|
||||||
|
logger.error("❌ self.cial est None")
|
||||||
|
# ✅ INITIALISER UN CACHE VIDE même en cas d'erreur
|
||||||
|
self._cache_fournisseurs = []
|
||||||
|
self._cache_fournisseurs_dict = {}
|
||||||
|
self._cache_fournisseurs_last_update = None
|
||||||
|
return
|
||||||
|
|
||||||
|
fournisseurs = []
|
||||||
|
fournisseurs_dict = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._com_context(), self._lock_com:
|
||||||
|
logger.info("=" * 80)
|
||||||
|
logger.info("🔄 DÉBUT REFRESH CACHE FOURNISSEURS")
|
||||||
|
logger.info("=" * 80)
|
||||||
|
|
||||||
|
# ✅ Accéder à FactoryFournisseur
|
||||||
|
try:
|
||||||
|
factory = self.cial.CptaApplication.FactoryFournisseur
|
||||||
|
logger.info("✅ FactoryFournisseur accessible")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Impossible d'accéder à FactoryFournisseur: {e}")
|
||||||
|
# ✅ INITIALISER UN CACHE VIDE
|
||||||
|
with self._lock_clients:
|
||||||
|
self._cache_fournisseurs = []
|
||||||
|
self._cache_fournisseurs_dict = {}
|
||||||
|
self._cache_fournisseurs_last_update = None
|
||||||
|
return
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
max_erreurs = 10 # ✅ RÉDUIT pour éviter de bloquer le démarrage
|
||||||
|
|
||||||
|
while index < 10000 and erreurs_consecutives < max_erreurs:
|
||||||
|
persist = None
|
||||||
|
|
||||||
|
# ✅ ÉTAPE 1 : Lire l'élément (avec gestion d'erreur simple)
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Index {index}: factory.List() échoue - {e}")
|
||||||
|
erreurs_consecutives += 1
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if persist is None:
|
||||||
|
logger.info(f"✅ Fin de liste à l'index {index}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# ✅ ÉTAPE 2 : Cast (avec gestion d'erreur simple)
|
||||||
|
obj = None
|
||||||
|
try:
|
||||||
|
obj = self._cast_client(persist)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Index {index}: _cast_client() échoue - {e}")
|
||||||
|
erreurs_consecutives += 1
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
logger.debug(f"⚠️ Index {index}: _cast_client retourne None (skip)")
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ✅ ÉTAPE 3 : Extraire
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
data = self._extraire_client(obj)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Index {index}: _extraire_client() échoue - {e}")
|
||||||
|
erreurs_consecutives += 1
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ✅ ÉTAPE 4 : Vérifier les données
|
||||||
|
if not data or not data.get("numero"):
|
||||||
|
logger.debug(f"⚠️ Index {index}: données invalides (skip)")
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ✅ SUCCÈS : Marquer et stocker
|
||||||
|
data["est_fournisseur"] = True
|
||||||
|
fournisseurs.append(data)
|
||||||
|
fournisseurs_dict[data["numero"]] = data
|
||||||
|
erreurs_consecutives = 0 # ✅ Reset compteur
|
||||||
|
|
||||||
|
# Log progression tous les 10
|
||||||
|
if len(fournisseurs) % 10 == 0:
|
||||||
|
logger.info(f" ✅ {len(fournisseurs)} fournisseurs chargés...")
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
# ✅ TOUJOURS stocker dans les attributs (même si vide)
|
||||||
|
with self._lock_clients:
|
||||||
|
self._cache_fournisseurs = fournisseurs
|
||||||
|
self._cache_fournisseurs_dict = fournisseurs_dict
|
||||||
|
self._cache_fournisseurs_last_update = datetime.now()
|
||||||
|
|
||||||
|
logger.info("=" * 80)
|
||||||
|
logger.info(f"✅ CACHE FOURNISSEURS ACTUALISÉ: {len(fournisseurs)} fournisseurs")
|
||||||
|
logger.info("=" * 80)
|
||||||
|
|
||||||
|
# ✅ Exemples
|
||||||
|
if len(fournisseurs) > 0:
|
||||||
|
logger.info("Exemples de fournisseurs chargés:")
|
||||||
|
for f in fournisseurs[:3]:
|
||||||
|
logger.info(f" - {f['numero']}: {f['intitule']}")
|
||||||
|
else:
|
||||||
|
logger.warning("⚠️ AUCUN FOURNISSEUR CHARGÉ")
|
||||||
|
logger.warning(f"Erreurs consécutives finales: {erreurs_consecutives}")
|
||||||
|
logger.warning(f"Dernier index testé: {index}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ ERREUR GLOBALE refresh fournisseurs: {e}", exc_info=True)
|
||||||
|
# ✅ INITIALISER UN CACHE VIDE en cas d'erreur critique
|
||||||
|
with self._lock_clients:
|
||||||
|
self._cache_fournisseurs = []
|
||||||
|
self._cache_fournisseurs_dict = {}
|
||||||
|
self._cache_fournisseurs_last_update = None
|
||||||
|
|
||||||
|
|
||||||
|
def lister_tous_fournisseurs(self, filtre=""):
|
||||||
|
"""
|
||||||
|
✅ CORRIGÉ : Liste les fournisseurs depuis le cache dédié
|
||||||
|
"""
|
||||||
|
# Si le cache fournisseurs n'existe pas ou est vide, le créer
|
||||||
|
if not hasattr(self, '_cache_fournisseurs') or len(self._cache_fournisseurs) == 0:
|
||||||
|
logger.info("Cache fournisseurs vide, chargement initial...")
|
||||||
|
self._refresh_cache_fournisseurs()
|
||||||
|
|
||||||
|
with self._lock_clients:
|
||||||
|
if not filtre:
|
||||||
|
result = self._cache_fournisseurs.copy()
|
||||||
|
logger.info(f"Liste fournisseurs sans filtre: {len(result)} résultats")
|
||||||
|
return result
|
||||||
|
|
||||||
|
filtre_lower = filtre.lower()
|
||||||
|
result = [
|
||||||
|
f for f in self._cache_fournisseurs
|
||||||
|
if filtre_lower in f["numero"].lower() or
|
||||||
|
filtre_lower in f["intitule"].lower()
|
||||||
|
]
|
||||||
|
logger.info(f"Liste fournisseurs avec filtre '{filtre}': {len(result)} résultats")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def lire_fournisseur(self, code_fournisseur):
|
||||||
|
"""
|
||||||
|
✅ CORRIGÉ : Lecture depuis le cache fournisseurs
|
||||||
|
"""
|
||||||
|
# Si le cache fournisseurs n'existe pas, le créer
|
||||||
|
if not hasattr(self, '_cache_fournisseurs_dict') or not self._cache_fournisseurs_dict:
|
||||||
|
self._refresh_cache_fournisseurs()
|
||||||
|
|
||||||
|
with self._lock_clients:
|
||||||
|
return self._cache_fournisseurs_dict.get(code_fournisseur)
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# API PUBLIQUE (ultra-rapide grâce au cache)
|
# API PUBLIQUE (ultra-rapide grâce au cache)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
@ -342,15 +512,23 @@ class SageConnector:
|
||||||
|
|
||||||
def forcer_actualisation_cache(self):
|
def forcer_actualisation_cache(self):
|
||||||
"""Force l'actualisation immédiate du cache (endpoint admin)"""
|
"""Force l'actualisation immédiate du cache (endpoint admin)"""
|
||||||
logger.info("Actualisation forcée du cache...")
|
logger.info("🔄 Actualisation forcée du cache...")
|
||||||
self._refresh_cache_clients()
|
self._refresh_cache_clients()
|
||||||
self._refresh_cache_articles()
|
self._refresh_cache_articles()
|
||||||
|
self._refresh_cache_fournisseurs() # ✅ AJOUT
|
||||||
|
logger.info("✅ Cache actualisé")
|
||||||
|
|
||||||
|
# ✅ AJOUT
|
||||||
|
if hasattr(self, '_refresh_cache_fournisseurs'):
|
||||||
|
self._refresh_cache_fournisseurs()
|
||||||
|
|
||||||
logger.info("Cache actualisé")
|
logger.info("Cache actualisé")
|
||||||
|
|
||||||
|
|
||||||
def get_cache_info(self):
|
def get_cache_info(self):
|
||||||
"""Retourne les infos du cache (endpoint monitoring)"""
|
"""Retourne les infos du cache (endpoint monitoring)"""
|
||||||
with self._lock_clients, self._lock_articles:
|
with self._lock_clients:
|
||||||
return {
|
info = {
|
||||||
"clients": {
|
"clients": {
|
||||||
"count": len(self._cache_clients),
|
"count": len(self._cache_clients),
|
||||||
"last_update": (
|
"last_update": (
|
||||||
|
|
@ -359,10 +537,7 @@ class SageConnector:
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
"age_minutes": (
|
"age_minutes": (
|
||||||
(
|
(datetime.now() - self._cache_clients_last_update).total_seconds() / 60
|
||||||
datetime.now() - self._cache_clients_last_update
|
|
||||||
).total_seconds()
|
|
||||||
/ 60
|
|
||||||
if self._cache_clients_last_update
|
if self._cache_clients_last_update
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
|
|
@ -375,17 +550,32 @@ class SageConnector:
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
"age_minutes": (
|
"age_minutes": (
|
||||||
(
|
(datetime.now() - self._cache_articles_last_update).total_seconds() / 60
|
||||||
datetime.now() - self._cache_articles_last_update
|
|
||||||
).total_seconds()
|
|
||||||
/ 60
|
|
||||||
if self._cache_articles_last_update
|
if self._cache_articles_last_update
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
},
|
}
|
||||||
"ttl_minutes": self._cache_ttl_minutes,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ✅ AJOUT : Info fournisseurs
|
||||||
|
if hasattr(self, '_cache_fournisseurs'):
|
||||||
|
info["fournisseurs"] = {
|
||||||
|
"count": len(self._cache_fournisseurs),
|
||||||
|
"last_update": (
|
||||||
|
self._cache_fournisseurs_last_update.isoformat()
|
||||||
|
if self._cache_fournisseurs_last_update
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"age_minutes": (
|
||||||
|
(datetime.now() - self._cache_fournisseurs_last_update).total_seconds() / 60
|
||||||
|
if self._cache_fournisseurs_last_update
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
info["ttl_minutes"] = self._cache_ttl_minutes
|
||||||
|
return info
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# CAST HELPERS
|
# CAST HELPERS
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
@ -395,7 +585,8 @@ class SageConnector:
|
||||||
obj = win32com.client.CastTo(persist_obj, "IBOClient3")
|
obj = win32com.client.CastTo(persist_obj, "IBOClient3")
|
||||||
obj.Read()
|
obj.Read()
|
||||||
return obj
|
return obj
|
||||||
except:
|
except Exception as e:
|
||||||
|
logger.debug(f"❌ _cast_client échoue: {e}") # ✅ AJOUTER CE LOG
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _cast_article(self, persist_obj):
|
def _cast_article(self, persist_obj):
|
||||||
|
|
@ -412,31 +603,72 @@ class SageConnector:
|
||||||
|
|
||||||
def _extraire_client(self, client_obj):
|
def _extraire_client(self, client_obj):
|
||||||
"""MISE À JOUR : Extraction avec détection prospect ET type"""
|
"""MISE À JOUR : Extraction avec détection prospect ET type"""
|
||||||
data = {
|
|
||||||
"numero": getattr(client_obj, "CT_Num", ""),
|
|
||||||
"intitule": getattr(client_obj, "CT_Intitule", ""),
|
|
||||||
"type": getattr(client_obj, "CT_Type", 0), # ✅ 0=Client/Prospect, 1=Fournisseur
|
|
||||||
"est_prospect": getattr(client_obj, "CT_Prospect", 0) == 1, # ✅ Indicateur prospect
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
adresse = getattr(client_obj, "Adresse", None)
|
# ✅ LOGS DÉTAILLÉS
|
||||||
if adresse:
|
try:
|
||||||
data["adresse"] = getattr(adresse, "Adresse", "")
|
numero = getattr(client_obj, "CT_Num", "")
|
||||||
data["code_postal"] = getattr(adresse, "CodePostal", "")
|
except Exception as e:
|
||||||
data["ville"] = getattr(adresse, "Ville", "")
|
logger.error(f"❌ Erreur lecture CT_Num: {e}")
|
||||||
except:
|
raise
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
telecom = getattr(client_obj, "Telecom", None)
|
intitule = getattr(client_obj, "CT_Intitule", "")
|
||||||
if telecom:
|
except Exception as e:
|
||||||
data["telephone"] = getattr(telecom, "Telephone", "")
|
logger.error(f"❌ Erreur lecture CT_Intitule sur {numero}: {e}")
|
||||||
data["email"] = getattr(telecom, "EMail", "")
|
raise
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return data
|
try:
|
||||||
|
type_tiers = getattr(client_obj, "CT_Type", 0)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur lecture CT_Type sur {numero}: {e}")
|
||||||
|
type_tiers = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
qualite = getattr(client_obj, "CT_Qualite", None)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Erreur lecture CT_Qualite sur {numero}: {e}")
|
||||||
|
qualite = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
prospect = getattr(client_obj, "CT_Prospect", 0) == 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Erreur lecture CT_Prospect sur {numero}: {e}")
|
||||||
|
prospect = False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"numero": numero,
|
||||||
|
"intitule": intitule,
|
||||||
|
"type": type_tiers,
|
||||||
|
"qualite": qualite,
|
||||||
|
"est_prospect": prospect,
|
||||||
|
"est_fournisseur": qualite in [2, 3] if qualite is not None else False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Adresse (non critique)
|
||||||
|
try:
|
||||||
|
adresse = getattr(client_obj, "Adresse", None)
|
||||||
|
if adresse:
|
||||||
|
data["adresse"] = getattr(adresse, "Adresse", "")
|
||||||
|
data["code_postal"] = getattr(adresse, "CodePostal", "")
|
||||||
|
data["ville"] = getattr(adresse, "Ville", "")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Erreur adresse sur {numero}: {e}")
|
||||||
|
|
||||||
|
# Telecom (non critique)
|
||||||
|
try:
|
||||||
|
telecom = getattr(client_obj, "Telecom", None)
|
||||||
|
if telecom:
|
||||||
|
data["telephone"] = getattr(telecom, "Telephone", "")
|
||||||
|
data["email"] = getattr(telecom, "EMail", "")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"⚠️ Erreur telecom sur {numero}: {e}")
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ ERREUR GLOBALE _extraire_client: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
def _extraire_article(self, article_obj):
|
def _extraire_article(self, article_obj):
|
||||||
return {
|
return {
|
||||||
|
|
@ -1881,34 +2113,6 @@ class SageConnector:
|
||||||
return prospect
|
return prospect
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# =========================================================================
|
|
||||||
# FOURNISSEURS (CT_Type = 1)
|
|
||||||
# =========================================================================
|
|
||||||
def lister_tous_fournisseurs(self, filtre=""):
|
|
||||||
"""Liste tous les fournisseurs depuis le cache"""
|
|
||||||
with self._lock_clients:
|
|
||||||
if not filtre:
|
|
||||||
return [c for c in self._cache_clients if c.get("type") == 1]
|
|
||||||
|
|
||||||
filtre_lower = filtre.lower()
|
|
||||||
return [
|
|
||||||
c
|
|
||||||
for c in self._cache_clients
|
|
||||||
if c.get("type") == 1
|
|
||||||
and (
|
|
||||||
filtre_lower in c["numero"].lower()
|
|
||||||
or filtre_lower in c["intitule"].lower()
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def lire_fournisseur(self, code_fournisseur):
|
|
||||||
"""Retourne un fournisseur depuis le cache"""
|
|
||||||
with self._lock_clients:
|
|
||||||
fournisseur = self._cache_clients_dict.get(code_fournisseur)
|
|
||||||
if fournisseur and fournisseur.get("type") == 1:
|
|
||||||
return fournisseur
|
|
||||||
return None
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# EXTRACTION CLIENTS (Mise à jour pour inclure prospects)
|
# EXTRACTION CLIENTS (Mise à jour pour inclure prospects)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue