dates and reference correctly done. Now, testing PDF generation
This commit is contained in:
parent
5257be0680
commit
0f9b3dfa0d
3 changed files with 2129 additions and 1504 deletions
142
main.py
142
main.py
|
|
@ -343,7 +343,7 @@ class FamilleCreate(BaseModel):
|
||||||
def verify_token(x_sage_token: str = Header(...)):
|
def verify_token(x_sage_token: str = Header(...)):
|
||||||
"""Vérification du token d'authentification"""
|
"""Vérification du token d'authentification"""
|
||||||
if x_sage_token != settings.sage_gateway_token:
|
if x_sage_token != settings.sage_gateway_token:
|
||||||
logger.warning(f"❌ Token invalide reçu: {x_sage_token[:20]}...")
|
logger.warning(f" Token invalide reçu: {x_sage_token[:20]}...")
|
||||||
raise HTTPException(401, "Token invalide")
|
raise HTTPException(401, "Token invalide")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -380,9 +380,9 @@ def startup():
|
||||||
# Validation config
|
# Validation config
|
||||||
try:
|
try:
|
||||||
validate_settings()
|
validate_settings()
|
||||||
logger.info("✅ Configuration validée")
|
logger.info(" Configuration validée")
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.error(f"❌ Configuration invalide: {e}")
|
logger.error(f" Configuration invalide: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Connexion Sage
|
# Connexion Sage
|
||||||
|
|
@ -391,9 +391,9 @@ def startup():
|
||||||
)
|
)
|
||||||
|
|
||||||
if not sage.connecter():
|
if not sage.connecter():
|
||||||
raise RuntimeError("❌ Impossible de se connecter à Sage 100c")
|
raise RuntimeError(" Impossible de se connecter à Sage 100c")
|
||||||
|
|
||||||
logger.info("✅ Sage Gateway démarré et connecté")
|
logger.info(" Sage Gateway démarré et connecté")
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
|
|
@ -519,7 +519,6 @@ def creer_devis(req: DevisRequest):
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_devis": req.date_devis or date.today(),
|
"date_devis": req.date_devis or date.today(),
|
||||||
"date_livraison": req.date_livraison or date.today(),
|
"date_livraison": req.date_livraison or date.today(),
|
||||||
"date_expedition": req.date_expedition or date.today(),
|
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
"lignes": req.lignes,
|
"lignes": req.lignes,
|
||||||
}
|
}
|
||||||
|
|
@ -534,7 +533,7 @@ def creer_devis(req: DevisRequest):
|
||||||
@app.post("/sage/devis/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/devis/get", dependencies=[Depends(verify_token)])
|
||||||
def lire_devis(req: CodeRequest):
|
def lire_devis(req: CodeRequest):
|
||||||
try:
|
try:
|
||||||
# ✅ Lecture complète depuis Sage (avec lignes)
|
# Lecture complète depuis Sage (avec lignes)
|
||||||
devis = sage.lire_devis(req.code)
|
devis = sage.lire_devis(req.code)
|
||||||
if not devis:
|
if not devis:
|
||||||
raise HTTPException(404, f"Devis {req.code} non trouvé")
|
raise HTTPException(404, f"Devis {req.code} non trouvé")
|
||||||
|
|
@ -553,7 +552,7 @@ def devis_list(
|
||||||
filtre: str = Query("", description="Filtre texte (numero, client)"),
|
filtre: str = Query("", description="Filtre texte (numero, client)"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# ✅ Récupération depuis le cache (instantané)
|
# Récupération depuis le cache (instantané)
|
||||||
devis_list = sage.lister_tous_devis_cache(filtre)
|
devis_list = sage.lister_tous_devis_cache(filtre)
|
||||||
|
|
||||||
# Filtrer par statut si demandé
|
# Filtrer par statut si demandé
|
||||||
|
|
@ -563,12 +562,12 @@ def devis_list(
|
||||||
# Limiter le nombre de résultats
|
# Limiter le nombre de résultats
|
||||||
devis_list = devis_list[:limit]
|
devis_list = devis_list[:limit]
|
||||||
|
|
||||||
logger.info(f"✅ {len(devis_list)} devis retournés depuis le cache")
|
logger.info(f" {len(devis_list)} devis retournés depuis le cache")
|
||||||
|
|
||||||
return {"success": True, "data": devis_list}
|
return {"success": True, "data": devis_list}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur liste devis: {e}", exc_info=True)
|
logger.error(f" Erreur liste devis: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -590,7 +589,7 @@ def changer_statut_devis_endpoint(numero: str, nouveau_statut: int):
|
||||||
doc.DO_Statut = nouveau_statut
|
doc.DO_Statut = nouveau_statut
|
||||||
doc.Write()
|
doc.Write()
|
||||||
|
|
||||||
logger.info(f"✅ Statut devis {numero}: {statut_actuel} → {nouveau_statut}")
|
logger.info(f" Statut devis {numero}: {statut_actuel} → {nouveau_statut}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
@ -637,7 +636,7 @@ def transformer_document(
|
||||||
f"(type {type_source}) → type {type_cible}"
|
f"(type {type_source}) → type {type_cible}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ✅ Matrice des transformations valides pour VOTRE Sage
|
# Matrice des transformations valides pour VOTRE Sage
|
||||||
transformations_valides = {
|
transformations_valides = {
|
||||||
(0, 10), # Devis → Commande
|
(0, 10), # Devis → Commande
|
||||||
(10, 30), # Commande → Bon de livraison
|
(10, 30), # Commande → Bon de livraison
|
||||||
|
|
@ -648,7 +647,7 @@ def transformer_document(
|
||||||
|
|
||||||
if (type_source, type_cible) not in transformations_valides:
|
if (type_source, type_cible) not in transformations_valides:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"❌ Transformation non autorisée: {type_source} → {type_cible}"
|
f" Transformation non autorisée: {type_source} → {type_cible}"
|
||||||
)
|
)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
400,
|
400,
|
||||||
|
|
@ -660,7 +659,7 @@ def transformer_document(
|
||||||
resultat = sage.transformer_document(numero_source, type_source, type_cible)
|
resultat = sage.transformer_document(numero_source, type_source, type_cible)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"✅ Transformation réussie: {numero_source} → "
|
f" Transformation réussie: {numero_source} → "
|
||||||
f"{resultat.get('document_cible', '?')} "
|
f"{resultat.get('document_cible', '?')} "
|
||||||
f"({resultat.get('nb_lignes', 0)} lignes)"
|
f"({resultat.get('nb_lignes', 0)} lignes)"
|
||||||
)
|
)
|
||||||
|
|
@ -670,10 +669,10 @@ def transformer_document(
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.error(f"❌ Erreur métier transformation: {e}")
|
logger.error(f" Erreur métier transformation: {e}")
|
||||||
raise HTTPException(400, str(e))
|
raise HTTPException(400, str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur technique transformation: {e}", exc_info=True)
|
logger.error(f" Erreur technique transformation: {e}", exc_info=True)
|
||||||
raise HTTPException(500, f"Erreur transformation: {str(e)}")
|
raise HTTPException(500, f"Erreur transformation: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -729,7 +728,7 @@ def commandes_list(
|
||||||
return {"success": True, "data": commandes}
|
return {"success": True, "data": commandes}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur liste commandes: {e}", exc_info=True)
|
logger.error(f" Erreur liste commandes: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -747,12 +746,12 @@ def factures_list(
|
||||||
|
|
||||||
factures = factures[:limit]
|
factures = factures[:limit]
|
||||||
|
|
||||||
logger.info(f"✅ {len(factures)} factures retournées depuis le cache")
|
logger.info(f" {len(factures)} factures retournées depuis le cache")
|
||||||
|
|
||||||
return {"success": True, "data": factures}
|
return {"success": True, "data": factures}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur liste factures: {e}", exc_info=True)
|
logger.error(f" Erreur liste factures: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -772,7 +771,7 @@ def lire_remise_max_client(code: str):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logger.info(f"✅ Remise max client {code}: {remise_max}%")
|
logger.info(f" Remise max client {code}: {remise_max}%")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
@ -847,15 +846,15 @@ def prospect_get(req: CodeRequest):
|
||||||
@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(req: FiltreRequest):
|
||||||
try:
|
try:
|
||||||
# ✅ Utiliser le cache au lieu de la lecture directe
|
# Utiliser le cache au lieu de la lecture directe
|
||||||
fournisseurs = sage.lister_tous_fournisseurs_cache(req.filtre)
|
fournisseurs = sage.lister_tous_fournisseurs_cache(req.filtre)
|
||||||
|
|
||||||
logger.info(f"✅ {len(fournisseurs)} fournisseurs retournés depuis le cache")
|
logger.info(f" {len(fournisseurs)} fournisseurs retournés depuis le cache")
|
||||||
|
|
||||||
return {"success": True, "data": fournisseurs}
|
return {"success": True, "data": fournisseurs}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur liste fournisseurs: {e}", exc_info=True)
|
logger.error(f" Erreur liste fournisseurs: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -865,7 +864,7 @@ def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
||||||
# Appel au connecteur Sage
|
# Appel au connecteur Sage
|
||||||
resultat = sage.creer_fournisseur(req.dict())
|
resultat = sage.creer_fournisseur(req.dict())
|
||||||
|
|
||||||
logger.info(f"✅ Fournisseur créé: {resultat.get('numero')}")
|
logger.info(f" Fournisseur créé: {resultat.get('numero')}")
|
||||||
|
|
||||||
return {"success": True, "data": resultat}
|
return {"success": True, "data": resultat}
|
||||||
|
|
||||||
|
|
@ -876,7 +875,7 @@ def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Erreur technique (ex: COM)
|
# Erreur technique (ex: COM)
|
||||||
logger.error(f"❌ Erreur technique création fournisseur: {e}")
|
logger.error(f" Erreur technique création fournisseur: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -898,7 +897,7 @@ def modifier_fournisseur_endpoint(req: FournisseurUpdateGatewayRequest):
|
||||||
@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):
|
||||||
"""
|
"""
|
||||||
✅ NOUVEAU : 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)
|
||||||
|
|
@ -922,7 +921,7 @@ def avoirs_list(
|
||||||
filtre: str = Query("", description="Filtre texte"),
|
filtre: str = Query("", description="Filtre texte"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# ✅ Récupération depuis le cache (instantané)
|
# Récupération depuis le cache (instantané)
|
||||||
avoirs = sage.lister_tous_avoirs_cache(filtre)
|
avoirs = sage.lister_tous_avoirs_cache(filtre)
|
||||||
|
|
||||||
# Filtrer par statut si demandé
|
# Filtrer par statut si demandé
|
||||||
|
|
@ -932,26 +931,26 @@ def avoirs_list(
|
||||||
# Limiter le nombre de résultats
|
# Limiter le nombre de résultats
|
||||||
avoirs = avoirs[:limit]
|
avoirs = avoirs[:limit]
|
||||||
|
|
||||||
logger.info(f"✅ {len(avoirs)} avoirs retournés depuis le cache")
|
logger.info(f" {len(avoirs)} avoirs retournés depuis le cache")
|
||||||
|
|
||||||
return {"success": True, "data": avoirs}
|
return {"success": True, "data": avoirs}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur liste avoirs: {e}", exc_info=True)
|
logger.error(f" Erreur liste avoirs: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/avoirs/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/avoirs/get", dependencies=[Depends(verify_token)])
|
||||||
def avoir_get(req: CodeRequest):
|
def avoir_get(req: CodeRequest):
|
||||||
try:
|
try:
|
||||||
# ✅ Essayer le cache d'abord
|
# Essayer le cache d'abord
|
||||||
avoir = sage.lire_avoir_cache(req.code)
|
avoir = sage.lire_avoir_cache(req.code)
|
||||||
|
|
||||||
if avoir:
|
if avoir:
|
||||||
logger.info(f"✅ Avoir {req.code} retourné depuis le cache")
|
logger.info(f" Avoir {req.code} retourné depuis le cache")
|
||||||
return {"success": True, "data": avoir, "source": "cache"}
|
return {"success": True, "data": avoir, "source": "cache"}
|
||||||
|
|
||||||
# ❌ Pas dans le cache → Lecture directe depuis Sage
|
# Pas dans le cache → Lecture directe depuis Sage
|
||||||
logger.info(f"⚠️ Avoir {req.code} absent du cache, lecture depuis Sage...")
|
logger.info(f"⚠️ Avoir {req.code} absent du cache, lecture depuis Sage...")
|
||||||
avoir = sage.lire_avoir(req.code)
|
avoir = sage.lire_avoir(req.code)
|
||||||
|
|
||||||
|
|
@ -977,7 +976,7 @@ def livraisons_list(
|
||||||
filtre: str = Query("", description="Filtre texte"),
|
filtre: str = Query("", description="Filtre texte"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# ✅ Récupération depuis le cache (instantané)
|
# Récupération depuis le cache (instantané)
|
||||||
livraisons = sage.lister_toutes_livraisons_cache(filtre)
|
livraisons = sage.lister_toutes_livraisons_cache(filtre)
|
||||||
|
|
||||||
# Filtrer par statut si demandé
|
# Filtrer par statut si demandé
|
||||||
|
|
@ -987,26 +986,26 @@ def livraisons_list(
|
||||||
# Limiter le nombre de résultats
|
# Limiter le nombre de résultats
|
||||||
livraisons = livraisons[:limit]
|
livraisons = livraisons[:limit]
|
||||||
|
|
||||||
logger.info(f"✅ {len(livraisons)} livraisons retournées depuis le cache")
|
logger.info(f" {len(livraisons)} livraisons retournées depuis le cache")
|
||||||
|
|
||||||
return {"success": True, "data": livraisons}
|
return {"success": True, "data": livraisons}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur liste livraisons: {e}", exc_info=True)
|
logger.error(f" Erreur liste livraisons: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/livraisons/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/livraisons/get", dependencies=[Depends(verify_token)])
|
||||||
def livraison_get(req: CodeRequest):
|
def livraison_get(req: CodeRequest):
|
||||||
try:
|
try:
|
||||||
# ✅ Essayer le cache d'abord
|
# Essayer le cache d'abord
|
||||||
livraison = sage.lire_livraison_cache(req.code)
|
livraison = sage.lire_livraison_cache(req.code)
|
||||||
|
|
||||||
if livraison:
|
if livraison:
|
||||||
logger.info(f"✅ Livraison {req.code} retournée depuis le cache")
|
logger.info(f" Livraison {req.code} retournée depuis le cache")
|
||||||
return {"success": True, "data": livraison, "source": "cache"}
|
return {"success": True, "data": livraison, "source": "cache"}
|
||||||
|
|
||||||
# ❌ Pas dans le cache → Lecture directe depuis Sage
|
# Pas dans le cache → Lecture directe depuis Sage
|
||||||
logger.info(f"⚠️ Livraison {req.code} absente du cache, lecture depuis Sage...")
|
logger.info(f"⚠️ Livraison {req.code} absente du cache, lecture depuis Sage...")
|
||||||
livraison = sage.lire_livraison(req.code)
|
livraison = sage.lire_livraison(req.code)
|
||||||
|
|
||||||
|
|
@ -1049,7 +1048,6 @@ def creer_commande_endpoint(req: CommandeCreateRequest):
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_commande": req.date_commande or date.today(),
|
"date_commande": req.date_commande or date.today(),
|
||||||
"date_livraison": req.date_livraison or date.today(),
|
"date_livraison": req.date_livraison or date.today(),
|
||||||
"date_expedition": req.date_expedition or date.today(),
|
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
"lignes": req.lignes,
|
"lignes": req.lignes,
|
||||||
}
|
}
|
||||||
|
|
@ -1092,7 +1090,6 @@ def creer_livraison_endpoint(req: LivraisonCreateGatewayRequest):
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_livraison": req.date_livraison or date.today(),
|
"date_livraison": req.date_livraison or date.today(),
|
||||||
"date_livraison_prevue": req.date_livraison or date.today(),
|
"date_livraison_prevue": req.date_livraison or date.today(),
|
||||||
"date_expedition": req.date_expedition or date.today(),
|
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
"lignes": req.lignes,
|
"lignes": req.lignes,
|
||||||
}
|
}
|
||||||
|
|
@ -1135,7 +1132,6 @@ def creer_avoir_endpoint(req: AvoirCreateGatewayRequest):
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_avoir": req.date_avoir or date.today(),
|
"date_avoir": req.date_avoir or date.today(),
|
||||||
"date_livraison": req.date_livraison or date.today(),
|
"date_livraison": req.date_livraison or date.today(),
|
||||||
"date_expedition": req.date_expedition or date.today(),
|
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
"lignes": req.lignes,
|
"lignes": req.lignes,
|
||||||
}
|
}
|
||||||
|
|
@ -1181,7 +1177,6 @@ def creer_facture_endpoint(req: FactureCreateGatewayRequest):
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_facture": req.date_facture or date.today(),
|
"date_facture": req.date_facture or date.today(),
|
||||||
"date_livraison": req.date_livraison or date.today(),
|
"date_livraison": req.date_livraison or date.today(),
|
||||||
"date_expedition": req.date_expedition or date.today(),
|
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
"lignes": req.lignes,
|
"lignes": req.lignes,
|
||||||
}
|
}
|
||||||
|
|
@ -1368,7 +1363,7 @@ async def stats_familles():
|
||||||
@app.post("/sage/documents/generate-pdf", dependencies=[Depends(verify_token)])
|
@app.post("/sage/documents/generate-pdf", dependencies=[Depends(verify_token)])
|
||||||
def generer_pdf_document(req: PDFGenerationRequest):
|
def generer_pdf_document(req: PDFGenerationRequest):
|
||||||
try:
|
try:
|
||||||
logger.info(f"📄 Génération PDF: {req.doc_id} (type={req.type_doc})")
|
logger.info(f" Génération PDF: {req.doc_id} (type={req.type_doc})")
|
||||||
|
|
||||||
# Appel au connecteur Sage
|
# Appel au connecteur Sage
|
||||||
pdf_bytes = sage.generer_pdf_document(req.doc_id, req.type_doc)
|
pdf_bytes = sage.generer_pdf_document(req.doc_id, req.type_doc)
|
||||||
|
|
@ -1381,7 +1376,7 @@ def generer_pdf_document(req: PDFGenerationRequest):
|
||||||
|
|
||||||
pdf_base64 = base64.b64encode(pdf_bytes).decode("utf-8")
|
pdf_base64 = base64.b64encode(pdf_bytes).decode("utf-8")
|
||||||
|
|
||||||
logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets")
|
logger.info(f" PDF généré: {len(pdf_bytes)} octets")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
@ -1396,7 +1391,7 @@ def generer_pdf_document(req: PDFGenerationRequest):
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur génération PDF: {e}", exc_info=True)
|
logger.error(f" Erreur génération PDF: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1424,7 +1419,7 @@ def lister_depots():
|
||||||
depot = win32com.client.CastTo(persist, "IBODepot3")
|
depot = win32com.client.CastTo(persist, "IBODepot3")
|
||||||
depot.Read()
|
depot.Read()
|
||||||
|
|
||||||
# ✅ Lire les attributs identifiés
|
# Lire les attributs identifiés
|
||||||
code = ""
|
code = ""
|
||||||
numero = 0
|
numero = 0
|
||||||
intitule = ""
|
intitule = ""
|
||||||
|
|
@ -1500,7 +1495,7 @@ def lister_depots():
|
||||||
depots.append(depot_info)
|
depots.append(depot_info)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f" ✅ Dépôt {index}: code='{code}', compteur={numero}, intitulé='{intitule}'"
|
f" Dépôt {index}: code='{code}', compteur={numero}, intitulé='{intitule}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
|
|
@ -1514,11 +1509,11 @@ def lister_depots():
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logger.error(f"❌ Erreur inattendue index {index}: {e}")
|
logger.error(f" Erreur inattendue index {index}: {e}")
|
||||||
index += 1
|
index += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"✅ {len(depots)} dépôt(s) trouvé(s)")
|
logger.info(f" {len(depots)} dépôt(s) trouvé(s)")
|
||||||
|
|
||||||
if not depots:
|
if not depots:
|
||||||
return {
|
return {
|
||||||
|
|
@ -1540,13 +1535,13 @@ def lister_depots():
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lecture dépôts: {e}", exc_info=True)
|
logger.error(f" Erreur lecture dépôts: {e}", exc_info=True)
|
||||||
raise HTTPException(500, f"Erreur lecture dépôts: {str(e)}")
|
raise HTTPException(500, f"Erreur lecture dépôts: {str(e)}")
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur: {e}", exc_info=True)
|
logger.error(f" Erreur: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1569,7 +1564,7 @@ def creer_entree_stock(req: EntreeStockRequest):
|
||||||
# Appel au connecteur
|
# Appel au connecteur
|
||||||
resultat = sage.creer_entree_stock(entree_data)
|
resultat = sage.creer_entree_stock(entree_data)
|
||||||
|
|
||||||
logger.info(f"✅ [ENTREE STOCK] Créé : {resultat.get('numero')}")
|
logger.info(f" [ENTREE STOCK] Créé : {resultat.get('numero')}")
|
||||||
|
|
||||||
return {"success": True, "data": resultat}
|
return {"success": True, "data": resultat}
|
||||||
|
|
||||||
|
|
@ -1578,7 +1573,7 @@ def creer_entree_stock(req: EntreeStockRequest):
|
||||||
raise HTTPException(400, str(e))
|
raise HTTPException(400, str(e))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur technique entrée stock : {e}", exc_info=True)
|
logger.error(f" Erreur technique entrée stock : {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1601,7 +1596,7 @@ def creer_sortie_stock(req: SortieStockRequest):
|
||||||
# Appel au connecteur
|
# Appel au connecteur
|
||||||
resultat = sage.creer_sortie_stock(sortie_data)
|
resultat = sage.creer_sortie_stock(sortie_data)
|
||||||
|
|
||||||
logger.info(f"✅ [SORTIE STOCK] Créé : {resultat.get('numero')}")
|
logger.info(f" [SORTIE STOCK] Créé : {resultat.get('numero')}")
|
||||||
|
|
||||||
return {"success": True, "data": resultat}
|
return {"success": True, "data": resultat}
|
||||||
|
|
||||||
|
|
@ -1610,7 +1605,7 @@ def creer_sortie_stock(req: SortieStockRequest):
|
||||||
raise HTTPException(400, str(e))
|
raise HTTPException(400, str(e))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur technique sortie stock : {e}", exc_info=True)
|
logger.error(f" Erreur technique sortie stock : {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1627,7 +1622,7 @@ def lire_mouvement_stock(numero: str):
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur lecture mouvement : {e}")
|
logger.error(f" Erreur lecture mouvement : {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1639,7 +1634,7 @@ def lister_modeles_disponibles():
|
||||||
|
|
||||||
return {"success": True, "data": modeles}
|
return {"success": True, "data": modeles}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur listage modèles: {e}")
|
logger.error(f" Erreur listage modèles: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1651,12 +1646,12 @@ def generer_pdf_document(
|
||||||
base64_encode: bool = Query(True, description="Retourner en base64"),
|
base64_encode: bool = Query(True, description="Retourner en base64"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
📄 Génère un PDF d'un document Sage avec le modèle spécifié
|
Génère un PDF d'un document Sage avec le modèle spécifié
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# ✅ LOG pour debug
|
# LOG pour debug
|
||||||
logger.info(
|
logger.info(
|
||||||
f"📄 PDF Request: numero={numero}, type={type_doc}, modele={modele}, base64={base64_encode}"
|
f" PDF Request: numero={numero}, type={type_doc}, modele={modele}, base64={base64_encode}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Générer le PDF
|
# Générer le PDF
|
||||||
|
|
@ -1667,8 +1662,8 @@ def generer_pdf_document(
|
||||||
if not pdf_bytes:
|
if not pdf_bytes:
|
||||||
raise HTTPException(404, f"Impossible de générer le PDF pour {numero}")
|
raise HTTPException(404, f"Impossible de générer le PDF pour {numero}")
|
||||||
|
|
||||||
# ✅ LOG taille PDF
|
# LOG taille PDF
|
||||||
logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets")
|
logger.info(f" PDF généré: {len(pdf_bytes)} octets")
|
||||||
|
|
||||||
if base64_encode:
|
if base64_encode:
|
||||||
# Retour en JSON avec base64
|
# Retour en JSON avec base64
|
||||||
|
|
@ -1696,17 +1691,34 @@ def generer_pdf_document(
|
||||||
media_type="application/pdf",
|
media_type="application/pdf",
|
||||||
headers={
|
headers={
|
||||||
"Content-Disposition": f'inline; filename="{numero}.pdf"',
|
"Content-Disposition": f'inline; filename="{numero}.pdf"',
|
||||||
"Content-Length": str(len(pdf_bytes)), # ✅ Taille explicite
|
"Content-Length": str(len(pdf_bytes)), # Taille explicite
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.error(f"❌ Erreur métier: {e}")
|
logger.error(f" Erreur métier: {e}")
|
||||||
raise HTTPException(400, str(e))
|
raise HTTPException(400, str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur technique: {e}", exc_info=True)
|
logger.error(f" Erreur technique: {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/sage/test-API-transformation")
|
||||||
|
def generer_pdf_transformation_native(numero: str, type_doc: int):
|
||||||
|
try:
|
||||||
|
expliration = sage.generer_pdf_transformation_native(numero, type_doc)
|
||||||
|
|
||||||
|
if not expliration:
|
||||||
|
raise HTTPException(404, f"ERROR")
|
||||||
|
|
||||||
|
return {"success": True, "data": expliration}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f" Erreur exploration : {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
3135
sage_connector.py
3135
sage_connector.py
File diff suppressed because it is too large
Load diff
316
test.py
Normal file
316
test.py
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
"""
|
||||||
|
🧪 TEST GÉNÉRATION PDF AVEC CRYSTAL REPORTS
|
||||||
|
À lancer immédiatement après installation Crystal
|
||||||
|
"""
|
||||||
|
|
||||||
|
import win32com.client
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_crystal_pdf_simple():
|
||||||
|
"""
|
||||||
|
Test rapide de Crystal Reports - sans Sage
|
||||||
|
Vérifie juste que Crystal peut s'instancier et exporter
|
||||||
|
"""
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("🧪 TEST CRYSTAL REPORTS - INSTANCIATION SIMPLE")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Instancier Crystal
|
||||||
|
print("\n1. Instanciation Crystal Runtime...")
|
||||||
|
|
||||||
|
prog_ids = [
|
||||||
|
"CrystalRuntime.Application.140",
|
||||||
|
"CrystalRuntime.Application.13",
|
||||||
|
"CrystalRuntime.Application.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
crystal = None
|
||||||
|
prog_id_utilisee = None
|
||||||
|
|
||||||
|
for prog_id in prog_ids:
|
||||||
|
try:
|
||||||
|
crystal = win32com.client.Dispatch(prog_id)
|
||||||
|
prog_id_utilisee = prog_id
|
||||||
|
print(f" ✅ {prog_id}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ {prog_id}: {e}")
|
||||||
|
|
||||||
|
if not crystal:
|
||||||
|
print("\n❌ ÉCHEC : Aucune version de Crystal disponible")
|
||||||
|
print(" → Vérifier installation")
|
||||||
|
print(" → Redémarrer le serveur si juste installé")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"\n✅ Crystal opérationnel : {prog_id_utilisee}")
|
||||||
|
|
||||||
|
# 2. Vérifier méthodes disponibles
|
||||||
|
print("\n2. Vérification méthodes d'export...")
|
||||||
|
|
||||||
|
methodes_requises = ['OpenReport', 'Export']
|
||||||
|
|
||||||
|
for methode in methodes_requises:
|
||||||
|
if hasattr(crystal, methode):
|
||||||
|
print(f" ✅ {methode}()")
|
||||||
|
else:
|
||||||
|
print(f" ❌ {methode}() manquante")
|
||||||
|
|
||||||
|
print("\n✅ Crystal Reports est prêt pour générer des PDF !")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ ERREUR : {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def tester_pdf_avec_sage(sage_connector, numero: str, type_doc: int):
|
||||||
|
"""
|
||||||
|
Test de génération PDF via Sage avec Crystal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sage_connector: Instance de ton SageConnector
|
||||||
|
numero: Numéro document (ex: "FA00123")
|
||||||
|
type_doc: Type document (0=devis, 60=facture, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (success: bool, pdf_bytes: bytes, message: str)
|
||||||
|
"""
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print(f"🧪 TEST GÉNÉRATION PDF SAGE + CRYSTAL")
|
||||||
|
print(f" Document: {numero} (type={type_doc})")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Vérifier connexion Sage
|
||||||
|
if not sage_connector.cial:
|
||||||
|
return False, None, "Connexion Sage non établie"
|
||||||
|
|
||||||
|
print("\n1. ✅ Connexion Sage OK")
|
||||||
|
|
||||||
|
# 2. Vérifier Crystal
|
||||||
|
print("\n2. Vérification Crystal...")
|
||||||
|
|
||||||
|
prog_ids = [
|
||||||
|
"CrystalRuntime.Application.140",
|
||||||
|
"CrystalRuntime.Application.13",
|
||||||
|
"CrystalRuntime.Application.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
crystal_ok = False
|
||||||
|
for prog_id in prog_ids:
|
||||||
|
try:
|
||||||
|
test = win32com.client.Dispatch(prog_id)
|
||||||
|
crystal_ok = True
|
||||||
|
print(f" ✅ Crystal disponible : {prog_id}")
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not crystal_ok:
|
||||||
|
return False, None, "Crystal Reports non disponible - Redémarrer le serveur"
|
||||||
|
|
||||||
|
# 3. Chercher modèle .BGC
|
||||||
|
print("\n3. Recherche modèle Crystal...")
|
||||||
|
|
||||||
|
modeles = sage_connector.lister_modeles_crystal()
|
||||||
|
|
||||||
|
# Déterminer catégorie
|
||||||
|
mapping = {0: "devis", 10: "commandes", 30: "livraisons", 60: "factures", 50: "avoirs"}
|
||||||
|
categorie = mapping.get(type_doc, "autres")
|
||||||
|
|
||||||
|
if categorie not in modeles or not modeles[categorie]:
|
||||||
|
return False, None, f"Aucun modèle trouvé pour type {type_doc}"
|
||||||
|
|
||||||
|
modele = modeles[categorie][0]
|
||||||
|
chemin_modele = modele["chemin_complet"]
|
||||||
|
|
||||||
|
print(f" ✅ Modèle : {modele['fichier']}")
|
||||||
|
print(f" 📁 Chemin : {chemin_modele}")
|
||||||
|
|
||||||
|
if not os.path.exists(chemin_modele):
|
||||||
|
return False, None, f"Fichier modèle introuvable : {chemin_modele}"
|
||||||
|
|
||||||
|
# 4. Générer PDF
|
||||||
|
print("\n4. Génération PDF...")
|
||||||
|
print(" ⏳ Traitement en cours...")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Utiliser ta méthode existante
|
||||||
|
pdf_bytes = sage_connector.generer_pdf_document(
|
||||||
|
numero=numero,
|
||||||
|
type_doc=type_doc,
|
||||||
|
modele=modele["fichier"]
|
||||||
|
)
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
|
||||||
|
if pdf_bytes and len(pdf_bytes) > 500:
|
||||||
|
print(f"\n✅ PDF GÉNÉRÉ AVEC SUCCÈS !")
|
||||||
|
print(f" 📊 Taille : {len(pdf_bytes):,} octets")
|
||||||
|
print(f" ⏱️ Temps : {elapsed:.2f}s")
|
||||||
|
|
||||||
|
return True, pdf_bytes, "Succès"
|
||||||
|
else:
|
||||||
|
return False, None, "PDF généré mais vide ou corrompu"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
print(f"\n❌ ÉCHEC après {elapsed:.2f}s")
|
||||||
|
print(f" Erreur : {e}")
|
||||||
|
|
||||||
|
return False, None, str(e)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur test PDF : {e}", exc_info=True)
|
||||||
|
return False, None, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def analyser_erreur_generation(erreur_msg: str):
|
||||||
|
"""
|
||||||
|
Analyse une erreur de génération PDF et propose solutions
|
||||||
|
"""
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("🔍 ANALYSE DE L'ERREUR")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
erreur_lower = erreur_msg.lower()
|
||||||
|
|
||||||
|
if "access" in erreur_lower or "permission" in erreur_lower:
|
||||||
|
print("""
|
||||||
|
❌ PROBLÈME : Permissions fichiers
|
||||||
|
|
||||||
|
💡 SOLUTIONS :
|
||||||
|
1. Vérifier que le dossier temporaire est accessible :
|
||||||
|
icacls "C:\\Windows\\Temp" /grant Everyone:(OI)(CI)F /T
|
||||||
|
|
||||||
|
2. Vérifier permissions modèles .BGC :
|
||||||
|
icacls "C:\\Users\\Public\\Documents\\Sage" /grant Everyone:(OI)(CI)R /T
|
||||||
|
|
||||||
|
3. Exécuter l'application avec droits admin (temporairement)
|
||||||
|
""")
|
||||||
|
|
||||||
|
elif "not found" in erreur_lower or "introuvable" in erreur_lower:
|
||||||
|
print("""
|
||||||
|
❌ PROBLÈME : Fichier modèle introuvable
|
||||||
|
|
||||||
|
💡 SOLUTIONS :
|
||||||
|
1. Vérifier que le modèle .BGC existe :
|
||||||
|
dir "C:\\Users\\Public\\Documents\\Sage\\Entreprise 100c\\*.bgc" /s
|
||||||
|
|
||||||
|
2. Spécifier le chemin complet du modèle
|
||||||
|
|
||||||
|
3. Utiliser un modèle par défaut Sage
|
||||||
|
""")
|
||||||
|
|
||||||
|
elif "com" in erreur_lower or "dispatch" in erreur_lower:
|
||||||
|
print("""
|
||||||
|
❌ PROBLÈME : Erreur COM / Crystal Runtime
|
||||||
|
|
||||||
|
💡 SOLUTIONS :
|
||||||
|
1. REDÉMARRER LE SERVEUR (recommandé)
|
||||||
|
shutdown /r /t 60
|
||||||
|
|
||||||
|
2. Réenregistrer les DLL Crystal :
|
||||||
|
cd "C:\\Program Files\\SAP BusinessObjects\\Crystal Reports for .NET Framework 4.0\\Common\\SAP BusinessObjects Enterprise XI 4.0\\win64_x64"
|
||||||
|
regsvr32 /s crpe32.dll
|
||||||
|
regsvr32 /s crxf_pdf.dll
|
||||||
|
|
||||||
|
3. Vérifier que les services Crystal sont démarrés :
|
||||||
|
sc start "SAP Crystal Reports Processing Server"
|
||||||
|
""")
|
||||||
|
|
||||||
|
elif "database" in erreur_lower or "connexion" in erreur_lower:
|
||||||
|
print("""
|
||||||
|
❌ PROBLÈME : Connexion base de données
|
||||||
|
|
||||||
|
💡 SOLUTIONS :
|
||||||
|
1. Le modèle Crystal doit pouvoir se connecter à la base Sage
|
||||||
|
|
||||||
|
2. Vérifier les paramètres de connexion dans le modèle
|
||||||
|
|
||||||
|
3. Utiliser l'API Sage pour passer les données au lieu de
|
||||||
|
connecter Crystal directement à la base
|
||||||
|
""")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"""
|
||||||
|
❌ ERREUR : {erreur_msg}
|
||||||
|
|
||||||
|
💡 SOLUTIONS GÉNÉRIQUES :
|
||||||
|
1. Redémarrer le serveur
|
||||||
|
2. Vérifier les logs détaillés
|
||||||
|
3. Tester avec un modèle Crystal simple
|
||||||
|
4. Utiliser PDF custom en attendant
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
|
||||||
|
# Routes API pour tests
|
||||||
|
def creer_routes_test(app, sage_connector):
|
||||||
|
"""
|
||||||
|
Ajouter ces routes dans main.py pour tester facilement
|
||||||
|
"""
|
||||||
|
|
||||||
|
@app.get("/sage/test-crystal-simple")
|
||||||
|
async def test_crystal_simple():
|
||||||
|
"""Test Crystal sans Sage"""
|
||||||
|
success = test_crystal_pdf_simple()
|
||||||
|
return {
|
||||||
|
"success": success,
|
||||||
|
"message": "Crystal opérationnel" if success else "Crystal non disponible"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/sage/test-pdf-complet")
|
||||||
|
async def test_pdf_complet(
|
||||||
|
numero: str = "FA00123",
|
||||||
|
type_doc: int = 60
|
||||||
|
):
|
||||||
|
"""Test génération PDF complète avec Sage + Crystal"""
|
||||||
|
|
||||||
|
success, pdf_bytes, message = tester_pdf_avec_sage(
|
||||||
|
sage_connector, numero, type_doc
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
from fastapi.responses import Response
|
||||||
|
return Response(
|
||||||
|
content=pdf_bytes,
|
||||||
|
media_type="application/pdf",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f"attachment; filename={numero}.pdf"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Analyser erreur
|
||||||
|
analyser_erreur_generation(message)
|
||||||
|
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail={
|
||||||
|
"error": message,
|
||||||
|
"recommandation": "Consulter les logs pour analyse détaillée"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("🚀 LANCEMENT TESTS CRYSTAL REPORTS")
|
||||||
|
|
||||||
|
# Test 1 : Crystal seul
|
||||||
|
crystal_ok = test_crystal_pdf_simple()
|
||||||
|
|
||||||
|
if not crystal_ok:
|
||||||
|
print("\n⚠️ Crystal non opérationnel")
|
||||||
|
print(" → Redémarrer le serveur et relancer")
|
||||||
|
else:
|
||||||
|
print("\n✅ Crystal OK - Prêt pour génération PDF avec Sage")
|
||||||
Loading…
Reference in a new issue