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(...)):
|
||||
"""Vérification du token d'authentification"""
|
||||
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")
|
||||
return True
|
||||
|
||||
|
|
@ -380,9 +380,9 @@ def startup():
|
|||
# Validation config
|
||||
try:
|
||||
validate_settings()
|
||||
logger.info("✅ Configuration validée")
|
||||
logger.info(" Configuration validée")
|
||||
except ValueError as e:
|
||||
logger.error(f"❌ Configuration invalide: {e}")
|
||||
logger.error(f" Configuration invalide: {e}")
|
||||
raise
|
||||
|
||||
# Connexion Sage
|
||||
|
|
@ -391,9 +391,9 @@ def startup():
|
|||
)
|
||||
|
||||
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")
|
||||
|
|
@ -519,7 +519,6 @@ def creer_devis(req: DevisRequest):
|
|||
"client": {"code": req.client_id, "intitule": ""},
|
||||
"date_devis": req.date_devis or date.today(),
|
||||
"date_livraison": req.date_livraison or date.today(),
|
||||
"date_expedition": req.date_expedition or date.today(),
|
||||
"reference": req.reference,
|
||||
"lignes": req.lignes,
|
||||
}
|
||||
|
|
@ -534,7 +533,7 @@ def creer_devis(req: DevisRequest):
|
|||
@app.post("/sage/devis/get", dependencies=[Depends(verify_token)])
|
||||
def lire_devis(req: CodeRequest):
|
||||
try:
|
||||
# ✅ Lecture complète depuis Sage (avec lignes)
|
||||
# Lecture complète depuis Sage (avec lignes)
|
||||
devis = sage.lire_devis(req.code)
|
||||
if not devis:
|
||||
raise HTTPException(404, f"Devis {req.code} non trouvé")
|
||||
|
|
@ -553,7 +552,7 @@ def devis_list(
|
|||
filtre: str = Query("", description="Filtre texte (numero, client)"),
|
||||
):
|
||||
try:
|
||||
# ✅ Récupération depuis le cache (instantané)
|
||||
# Récupération depuis le cache (instantané)
|
||||
devis_list = sage.lister_tous_devis_cache(filtre)
|
||||
|
||||
# Filtrer par statut si demandé
|
||||
|
|
@ -563,12 +562,12 @@ def devis_list(
|
|||
# Limiter le nombre de résultats
|
||||
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}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -590,7 +589,7 @@ def changer_statut_devis_endpoint(numero: str, nouveau_statut: int):
|
|||
doc.DO_Statut = nouveau_statut
|
||||
doc.Write()
|
||||
|
||||
logger.info(f"✅ Statut devis {numero}: {statut_actuel} → {nouveau_statut}")
|
||||
logger.info(f" Statut devis {numero}: {statut_actuel} → {nouveau_statut}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
|
|
@ -637,7 +636,7 @@ def transformer_document(
|
|||
f"(type {type_source}) → type {type_cible}"
|
||||
)
|
||||
|
||||
# ✅ Matrice des transformations valides pour VOTRE Sage
|
||||
# Matrice des transformations valides pour VOTRE Sage
|
||||
transformations_valides = {
|
||||
(0, 10), # Devis → Commande
|
||||
(10, 30), # Commande → Bon de livraison
|
||||
|
|
@ -648,7 +647,7 @@ def transformer_document(
|
|||
|
||||
if (type_source, type_cible) not in transformations_valides:
|
||||
logger.error(
|
||||
f"❌ Transformation non autorisée: {type_source} → {type_cible}"
|
||||
f" Transformation non autorisée: {type_source} → {type_cible}"
|
||||
)
|
||||
raise HTTPException(
|
||||
400,
|
||||
|
|
@ -660,7 +659,7 @@ def transformer_document(
|
|||
resultat = sage.transformer_document(numero_source, type_source, type_cible)
|
||||
|
||||
logger.info(
|
||||
f"✅ Transformation réussie: {numero_source} → "
|
||||
f" Transformation réussie: {numero_source} → "
|
||||
f"{resultat.get('document_cible', '?')} "
|
||||
f"({resultat.get('nb_lignes', 0)} lignes)"
|
||||
)
|
||||
|
|
@ -670,10 +669,10 @@ def transformer_document(
|
|||
except HTTPException:
|
||||
raise
|
||||
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))
|
||||
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)}")
|
||||
|
||||
|
||||
|
|
@ -729,7 +728,7 @@ def commandes_list(
|
|||
return {"success": True, "data": commandes}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -747,12 +746,12 @@ def factures_list(
|
|||
|
||||
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}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -772,7 +771,7 @@ def lire_remise_max_client(code: str):
|
|||
except:
|
||||
pass
|
||||
|
||||
logger.info(f"✅ Remise max client {code}: {remise_max}%")
|
||||
logger.info(f" Remise max client {code}: {remise_max}%")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
|
|
@ -847,15 +846,15 @@ def prospect_get(req: CodeRequest):
|
|||
@app.post("/sage/fournisseurs/list", dependencies=[Depends(verify_token)])
|
||||
def fournisseurs_list(req: FiltreRequest):
|
||||
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)
|
||||
|
||||
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}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -865,7 +864,7 @@ def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
|||
# Appel au connecteur Sage
|
||||
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}
|
||||
|
||||
|
|
@ -876,7 +875,7 @@ def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
|||
|
||||
except Exception as e:
|
||||
# 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))
|
||||
|
||||
|
||||
|
|
@ -898,7 +897,7 @@ def modifier_fournisseur_endpoint(req: FournisseurUpdateGatewayRequest):
|
|||
@app.post("/sage/fournisseurs/get", dependencies=[Depends(verify_token)])
|
||||
def fournisseur_get(req: CodeRequest):
|
||||
"""
|
||||
✅ NOUVEAU : Lecture d'un fournisseur par code
|
||||
NOUVEAU : Lecture d'un fournisseur par code
|
||||
"""
|
||||
try:
|
||||
fournisseur = sage.lire_fournisseur(req.code)
|
||||
|
|
@ -922,7 +921,7 @@ def avoirs_list(
|
|||
filtre: str = Query("", description="Filtre texte"),
|
||||
):
|
||||
try:
|
||||
# ✅ Récupération depuis le cache (instantané)
|
||||
# Récupération depuis le cache (instantané)
|
||||
avoirs = sage.lister_tous_avoirs_cache(filtre)
|
||||
|
||||
# Filtrer par statut si demandé
|
||||
|
|
@ -932,26 +931,26 @@ def avoirs_list(
|
|||
# Limiter le nombre de résultats
|
||||
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}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
@app.post("/sage/avoirs/get", dependencies=[Depends(verify_token)])
|
||||
def avoir_get(req: CodeRequest):
|
||||
try:
|
||||
# ✅ Essayer le cache d'abord
|
||||
# Essayer le cache d'abord
|
||||
avoir = sage.lire_avoir_cache(req.code)
|
||||
|
||||
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"}
|
||||
|
||||
# ❌ 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...")
|
||||
avoir = sage.lire_avoir(req.code)
|
||||
|
||||
|
|
@ -977,7 +976,7 @@ def livraisons_list(
|
|||
filtre: str = Query("", description="Filtre texte"),
|
||||
):
|
||||
try:
|
||||
# ✅ Récupération depuis le cache (instantané)
|
||||
# Récupération depuis le cache (instantané)
|
||||
livraisons = sage.lister_toutes_livraisons_cache(filtre)
|
||||
|
||||
# Filtrer par statut si demandé
|
||||
|
|
@ -987,26 +986,26 @@ def livraisons_list(
|
|||
# Limiter le nombre de résultats
|
||||
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}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
@app.post("/sage/livraisons/get", dependencies=[Depends(verify_token)])
|
||||
def livraison_get(req: CodeRequest):
|
||||
try:
|
||||
# ✅ Essayer le cache d'abord
|
||||
# Essayer le cache d'abord
|
||||
livraison = sage.lire_livraison_cache(req.code)
|
||||
|
||||
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"}
|
||||
|
||||
# ❌ 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...")
|
||||
livraison = sage.lire_livraison(req.code)
|
||||
|
||||
|
|
@ -1049,7 +1048,6 @@ def creer_commande_endpoint(req: CommandeCreateRequest):
|
|||
"client": {"code": req.client_id, "intitule": ""},
|
||||
"date_commande": req.date_commande or date.today(),
|
||||
"date_livraison": req.date_livraison or date.today(),
|
||||
"date_expedition": req.date_expedition or date.today(),
|
||||
"reference": req.reference,
|
||||
"lignes": req.lignes,
|
||||
}
|
||||
|
|
@ -1092,7 +1090,6 @@ def creer_livraison_endpoint(req: LivraisonCreateGatewayRequest):
|
|||
"client": {"code": req.client_id, "intitule": ""},
|
||||
"date_livraison": 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,
|
||||
"lignes": req.lignes,
|
||||
}
|
||||
|
|
@ -1135,7 +1132,6 @@ def creer_avoir_endpoint(req: AvoirCreateGatewayRequest):
|
|||
"client": {"code": req.client_id, "intitule": ""},
|
||||
"date_avoir": req.date_avoir or date.today(),
|
||||
"date_livraison": req.date_livraison or date.today(),
|
||||
"date_expedition": req.date_expedition or date.today(),
|
||||
"reference": req.reference,
|
||||
"lignes": req.lignes,
|
||||
}
|
||||
|
|
@ -1181,7 +1177,6 @@ def creer_facture_endpoint(req: FactureCreateGatewayRequest):
|
|||
"client": {"code": req.client_id, "intitule": ""},
|
||||
"date_facture": req.date_facture or date.today(),
|
||||
"date_livraison": req.date_livraison or date.today(),
|
||||
"date_expedition": req.date_expedition or date.today(),
|
||||
"reference": req.reference,
|
||||
"lignes": req.lignes,
|
||||
}
|
||||
|
|
@ -1368,7 +1363,7 @@ async def stats_familles():
|
|||
@app.post("/sage/documents/generate-pdf", dependencies=[Depends(verify_token)])
|
||||
def generer_pdf_document(req: PDFGenerationRequest):
|
||||
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
|
||||
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")
|
||||
|
||||
logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets")
|
||||
logger.info(f" PDF généré: {len(pdf_bytes)} octets")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
|
|
@ -1396,7 +1391,7 @@ def generer_pdf_document(req: PDFGenerationRequest):
|
|||
except HTTPException:
|
||||
raise
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -1424,7 +1419,7 @@ def lister_depots():
|
|||
depot = win32com.client.CastTo(persist, "IBODepot3")
|
||||
depot.Read()
|
||||
|
||||
# ✅ Lire les attributs identifiés
|
||||
# Lire les attributs identifiés
|
||||
code = ""
|
||||
numero = 0
|
||||
intitule = ""
|
||||
|
|
@ -1500,7 +1495,7 @@ def lister_depots():
|
|||
depots.append(depot_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
|
||||
|
|
@ -1514,11 +1509,11 @@ def lister_depots():
|
|||
)
|
||||
break
|
||||
else:
|
||||
logger.error(f"❌ Erreur inattendue index {index}: {e}")
|
||||
logger.error(f" Erreur inattendue index {index}: {e}")
|
||||
index += 1
|
||||
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:
|
||||
return {
|
||||
|
|
@ -1540,13 +1535,13 @@ def lister_depots():
|
|||
}
|
||||
|
||||
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)}")
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -1569,7 +1564,7 @@ def creer_entree_stock(req: EntreeStockRequest):
|
|||
# Appel au connecteur
|
||||
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}
|
||||
|
||||
|
|
@ -1578,7 +1573,7 @@ def creer_entree_stock(req: EntreeStockRequest):
|
|||
raise HTTPException(400, str(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))
|
||||
|
||||
|
||||
|
|
@ -1601,7 +1596,7 @@ def creer_sortie_stock(req: SortieStockRequest):
|
|||
# Appel au connecteur
|
||||
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}
|
||||
|
||||
|
|
@ -1610,7 +1605,7 @@ def creer_sortie_stock(req: SortieStockRequest):
|
|||
raise HTTPException(400, str(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))
|
||||
|
||||
|
||||
|
|
@ -1627,7 +1622,7 @@ def lire_mouvement_stock(numero: str):
|
|||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Erreur lecture mouvement : {e}")
|
||||
logger.error(f" Erreur lecture mouvement : {e}")
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
|
|
@ -1639,7 +1634,7 @@ def lister_modeles_disponibles():
|
|||
|
||||
return {"success": True, "data": modeles}
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -1651,12 +1646,12 @@ def generer_pdf_document(
|
|||
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:
|
||||
# ✅ LOG pour debug
|
||||
# LOG pour debug
|
||||
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
|
||||
|
|
@ -1667,8 +1662,8 @@ def generer_pdf_document(
|
|||
if not pdf_bytes:
|
||||
raise HTTPException(404, f"Impossible de générer le PDF pour {numero}")
|
||||
|
||||
# ✅ LOG taille PDF
|
||||
logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets")
|
||||
# LOG taille PDF
|
||||
logger.info(f" PDF généré: {len(pdf_bytes)} octets")
|
||||
|
||||
if base64_encode:
|
||||
# Retour en JSON avec base64
|
||||
|
|
@ -1696,17 +1691,34 @@ def generer_pdf_document(
|
|||
media_type="application/pdf",
|
||||
headers={
|
||||
"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:
|
||||
raise
|
||||
except ValueError as e:
|
||||
logger.error(f"❌ Erreur métier: {e}")
|
||||
logger.error(f" Erreur métier: {e}")
|
||||
raise HTTPException(400, str(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))
|
||||
|
||||
|
||||
|
|
|
|||
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