dates and reference correctly done. Now, testing PDF generation

This commit is contained in:
mickael 2025-12-22 14:09:06 +01:00
parent 5257be0680
commit 0f9b3dfa0d
3 changed files with 2129 additions and 1504 deletions

142
main.py
View file

@ -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,20 +1691,37 @@ 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))
# =====================================================
# LANCEMENT
# =====================================================

File diff suppressed because it is too large Load diff

316
test.py Normal file
View 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")