feat: add PDF generation endpoint and optimize list endpoints
This commit is contained in:
parent
ec5a0f0089
commit
51f49298c2
1 changed files with 142 additions and 380 deletions
484
main.py
484
main.py
|
|
@ -185,6 +185,11 @@ class FactureUpdateGatewayRequest(BaseModel):
|
||||||
numero: str
|
numero: str
|
||||||
facture_data: Dict
|
facture_data: Dict
|
||||||
|
|
||||||
|
class PDFGenerationRequest(BaseModel):
|
||||||
|
"""Modèle pour génération PDF"""
|
||||||
|
doc_id: str = Field(..., description="Numéro du document")
|
||||||
|
type_doc: int = Field(..., ge=0, le=60, description="Type de document Sage")
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# SÉCURITÉ
|
# SÉCURITÉ
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -378,8 +383,14 @@ 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):
|
||||||
"""Lecture d'un devis"""
|
"""
|
||||||
|
📄 Lecture d'un devis AVEC ses lignes (lecture Sage directe)
|
||||||
|
|
||||||
|
⚠️ Plus lent que /list car charge les lignes depuis Sage
|
||||||
|
💡 Utiliser /list pour afficher une table rapide
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
|
# ✅ 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é")
|
||||||
|
|
@ -393,195 +404,35 @@ def lire_devis(req: CodeRequest):
|
||||||
|
|
||||||
@app.post("/sage/devis/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/devis/list", dependencies=[Depends(verify_token)])
|
||||||
def devis_list(
|
def devis_list(
|
||||||
limit: int = 100, statut: Optional[int] = None, inclure_lignes: bool = Query(True)
|
limit: int = Query(100, description="Nombre max de devis"),
|
||||||
|
statut: Optional[int] = Query(None, description="Filtrer par statut"),
|
||||||
|
filtre: str = Query("", description="Filtre texte (numero, client)")
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
📋 Liste tous les devis avec filtres optionnels
|
📋 Liste rapide des devis depuis le CACHE (sans lignes)
|
||||||
|
|
||||||
Args:
|
⚡ ULTRA-RAPIDE: Utilise le cache mémoire au lieu de scanner Sage
|
||||||
limit: Nombre max de devis à retourner
|
💡 Pour les détails avec lignes, utiliser GET /sage/devis/get
|
||||||
statut: Filtre par statut (optionnel)
|
|
||||||
inclure_lignes: Si True, charge les lignes de chaque devis (par défaut: True)
|
|
||||||
|
|
||||||
✅ AMÉLIORATION: Charge maintenant les lignes de chaque devis
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not sage or not sage.cial:
|
# ✅ Récupération depuis le cache (instantané)
|
||||||
raise HTTPException(503, "Service Sage indisponible")
|
devis_list = sage.lister_tous_devis_cache(filtre)
|
||||||
|
|
||||||
with sage._com_context(), sage._lock_com:
|
# Filtrer par statut si demandé
|
||||||
factory = sage.cial.FactoryDocumentVente
|
if statut is not None:
|
||||||
devis_list = []
|
devis_list = [d for d in devis_list if d.get("statut") == statut]
|
||||||
index = 1
|
|
||||||
max_iterations = limit * 3
|
|
||||||
erreurs_consecutives = 0
|
|
||||||
max_erreurs = 50
|
|
||||||
|
|
||||||
logger.info(
|
# Limiter le nombre de résultats
|
||||||
f"🔍 Recherche devis (limit={limit}, statut={statut}, inclure_lignes={inclure_lignes})"
|
devis_list = devis_list[:limit]
|
||||||
)
|
|
||||||
|
|
||||||
while (
|
logger.info(f"✅ {len(devis_list)} devis retournés depuis le cache")
|
||||||
len(devis_list) < limit
|
|
||||||
and index < max_iterations
|
|
||||||
and erreurs_consecutives < max_erreurs
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
persist = factory.List(index)
|
|
||||||
if persist is None:
|
|
||||||
logger.debug(f"Fin de liste à l'index {index}")
|
|
||||||
break
|
|
||||||
|
|
||||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
||||||
doc.Read()
|
|
||||||
|
|
||||||
# Filtrer uniquement devis (type 0)
|
|
||||||
doc_type = getattr(doc, "DO_Type", -1)
|
|
||||||
if doc_type != 0:
|
|
||||||
index += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
doc_statut = getattr(doc, "DO_Statut", 0)
|
|
||||||
|
|
||||||
# Filtre statut
|
|
||||||
if statut is not None and doc_statut != statut:
|
|
||||||
index += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ✅ Charger client via .Client
|
|
||||||
client_code = ""
|
|
||||||
client_intitule = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
client_obj = getattr(doc, "Client", None)
|
|
||||||
if client_obj:
|
|
||||||
client_obj.Read()
|
|
||||||
client_code = getattr(client_obj, "CT_Num", "").strip()
|
|
||||||
client_intitule = getattr(
|
|
||||||
client_obj, "CT_Intitule", ""
|
|
||||||
).strip()
|
|
||||||
logger.debug(
|
|
||||||
f"✅ Client: {client_code} - {client_intitule}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Erreur chargement client: {e}")
|
|
||||||
# Fallback sur cache si code disponible
|
|
||||||
if client_code:
|
|
||||||
client_cache = sage.lire_client(client_code)
|
|
||||||
if client_cache:
|
|
||||||
client_intitule = client_cache.get("intitule", "")
|
|
||||||
|
|
||||||
# ✅✅ NOUVEAU: Charger les lignes si demandé
|
|
||||||
lignes = []
|
|
||||||
if inclure_lignes:
|
|
||||||
try:
|
|
||||||
factory_lignes = getattr(doc, "FactoryDocumentLigne", None)
|
|
||||||
if not factory_lignes:
|
|
||||||
factory_lignes = getattr(
|
|
||||||
doc, "FactoryDocumentVenteLigne", None
|
|
||||||
)
|
|
||||||
|
|
||||||
if factory_lignes:
|
|
||||||
ligne_index = 1
|
|
||||||
while ligne_index <= 100: # Max 100 lignes par devis
|
|
||||||
try:
|
|
||||||
ligne_persist = factory_lignes.List(ligne_index)
|
|
||||||
if ligne_persist is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
ligne = win32com.client.CastTo(
|
|
||||||
ligne_persist, "IBODocumentLigne3"
|
|
||||||
)
|
|
||||||
ligne.Read()
|
|
||||||
|
|
||||||
# Charger référence article
|
|
||||||
article_ref = ""
|
|
||||||
try:
|
|
||||||
article_ref = getattr(
|
|
||||||
ligne, "AR_Ref", ""
|
|
||||||
).strip()
|
|
||||||
if not article_ref:
|
|
||||||
article_obj = getattr(
|
|
||||||
ligne, "Article", None
|
|
||||||
)
|
|
||||||
if article_obj:
|
|
||||||
article_obj.Read()
|
|
||||||
article_ref = getattr(
|
|
||||||
article_obj, "AR_Ref", ""
|
|
||||||
).strip()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
lignes.append(
|
|
||||||
{
|
|
||||||
"article": article_ref,
|
|
||||||
"designation": getattr(
|
|
||||||
ligne, "DL_Design", ""
|
|
||||||
),
|
|
||||||
"quantite": float(
|
|
||||||
getattr(ligne, "DL_Qte", 0.0)
|
|
||||||
),
|
|
||||||
"prix_unitaire": float(
|
|
||||||
getattr(
|
|
||||||
ligne, "DL_PrixUnitaire", 0.0
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"montant_ht": float(
|
|
||||||
getattr(ligne, "DL_MontantHT", 0.0)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ligne_index += 1
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Erreur ligne {ligne_index}: {e}")
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Erreur chargement lignes: {e}")
|
|
||||||
|
|
||||||
devis_list.append(
|
|
||||||
{
|
|
||||||
"numero": getattr(doc, "DO_Piece", ""),
|
|
||||||
"date": str(getattr(doc, "DO_Date", "")),
|
|
||||||
"client_code": client_code,
|
|
||||||
"client_intitule": client_intitule,
|
|
||||||
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)),
|
|
||||||
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)),
|
|
||||||
"statut": doc_statut,
|
|
||||||
"lignes": lignes, # ✅ Lignes incluses
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
erreurs_consecutives = 0
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
erreurs_consecutives += 1
|
|
||||||
logger.debug(f"⚠️ Erreur index {index}: {e}")
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
if erreurs_consecutives >= max_erreurs:
|
|
||||||
logger.warning(
|
|
||||||
f"⚠️ Arrêt après {max_erreurs} erreurs consécutives"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
nb_avec_client = sum(1 for d in devis_list if d["client_intitule"])
|
|
||||||
nb_avec_lignes = sum(1 for d in devis_list if d.get("lignes"))
|
|
||||||
logger.info(
|
|
||||||
f"✅ {len(devis_list)} devis retournés "
|
|
||||||
f"({nb_avec_client} avec client, {nb_avec_lignes} avec lignes)"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"success": True, "data": devis_list}
|
return {"success": True, "data": devis_list}
|
||||||
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
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))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/devis/statut", dependencies=[Depends(verify_token)])
|
@app.post("/sage/devis/statut", dependencies=[Depends(verify_token)])
|
||||||
def changer_statut_devis_endpoint(numero: str, nouveau_statut: int):
|
def changer_statut_devis_endpoint(numero: str, nouveau_statut: int):
|
||||||
"""Change le statut d'un devis"""
|
"""Change le statut d'un devis"""
|
||||||
|
|
@ -750,210 +601,58 @@ def contact_read(req: CodeRequest):
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/commandes/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/commandes/list", dependencies=[Depends(verify_token)])
|
||||||
def commandes_list(limit: int = 100, statut: Optional[int] = None):
|
def commandes_list(
|
||||||
"""
|
limit: int = Query(100, description="Nombre max de commandes"),
|
||||||
📋 Liste toutes les commandes
|
statut: Optional[int] = Query(None, description="Filtrer par statut"),
|
||||||
✅ AJOUT: Retourne maintenant DO_Ref pour tracer les transformations
|
filtre: str = Query("", description="Filtre texte")
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if not sage or not sage.cial:
|
|
||||||
raise HTTPException(503, "Service Sage indisponible")
|
|
||||||
|
|
||||||
with sage._com_context(), sage._lock_com:
|
|
||||||
factory = sage.cial.FactoryDocumentVente
|
|
||||||
commandes = []
|
|
||||||
index = 1
|
|
||||||
max_iterations = limit * 10
|
|
||||||
erreurs_consecutives = 0
|
|
||||||
max_erreurs = 100
|
|
||||||
|
|
||||||
logger.info(f"🔍 Recherche commandes (limit={limit}, statut={statut})")
|
|
||||||
|
|
||||||
while (
|
|
||||||
len(commandes) < limit
|
|
||||||
and index < max_iterations
|
|
||||||
and erreurs_consecutives < max_erreurs
|
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
📋 Liste rapide des commandes depuis le CACHE (sans lignes)
|
||||||
|
|
||||||
|
⚡ ULTRA-RAPIDE: Utilise le cache mémoire
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
persist = factory.List(index)
|
commandes = sage.lister_toutes_commandes_cache(filtre)
|
||||||
if persist is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
if statut is not None:
|
||||||
doc.Read()
|
commandes = [c for c in commandes if c.get("statut") == statut]
|
||||||
|
|
||||||
doc_type = getattr(doc, "DO_Type", -1)
|
commandes = commandes[:limit]
|
||||||
|
|
||||||
# Filtrer sur type 10 (BON_COMMANDE)
|
logger.info(f"✅ {len(commandes)} commandes retournées depuis le cache")
|
||||||
if doc_type != settings.SAGE_TYPE_BON_COMMANDE:
|
|
||||||
index += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
doc_statut = getattr(doc, "DO_Statut", 0)
|
|
||||||
|
|
||||||
# Filtre statut
|
|
||||||
if statut is not None and doc_statut != statut:
|
|
||||||
index += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Charger client
|
|
||||||
client_code = ""
|
|
||||||
client_intitule = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
client_obj = getattr(doc, "Client", None)
|
|
||||||
if client_obj:
|
|
||||||
client_obj.Read()
|
|
||||||
client_code = getattr(client_obj, "CT_Num", "").strip()
|
|
||||||
client_intitule = getattr(
|
|
||||||
client_obj, "CT_Intitule", ""
|
|
||||||
).strip()
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Erreur chargement client: {e}")
|
|
||||||
|
|
||||||
commande = {
|
|
||||||
"numero": getattr(doc, "DO_Piece", ""),
|
|
||||||
"reference": getattr(doc, "DO_Ref", ""), # ✅ AJOUT
|
|
||||||
"date": str(getattr(doc, "DO_Date", "")),
|
|
||||||
"client_code": client_code,
|
|
||||||
"client_intitule": client_intitule,
|
|
||||||
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)),
|
|
||||||
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)),
|
|
||||||
"statut": doc_statut,
|
|
||||||
}
|
|
||||||
|
|
||||||
commandes.append(commande)
|
|
||||||
erreurs_consecutives = 0
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
erreurs_consecutives += 1
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
if erreurs_consecutives >= max_erreurs:
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.info(f"✅ {len(commandes)} commandes retournées")
|
|
||||||
|
|
||||||
return {"success": True, "data": commandes}
|
return {"success": True, "data": commandes}
|
||||||
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
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))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/factures/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/factures/list", dependencies=[Depends(verify_token)])
|
||||||
def factures_list(limit: int = 100, statut: Optional[int] = None):
|
def factures_list(
|
||||||
|
limit: int = Query(100, description="Nombre max de factures"),
|
||||||
|
statut: Optional[int] = Query(None, description="Filtrer par statut"),
|
||||||
|
filtre: str = Query("", description="Filtre texte")
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
📋 Liste toutes les factures avec leurs lignes
|
📋 Liste rapide des factures depuis le CACHE (sans lignes)
|
||||||
✅ AJOUT: Retourne maintenant DO_Ref pour tracer les transformations
|
|
||||||
|
⚡ ULTRA-RAPIDE: Utilise le cache mémoire
|
||||||
|
💡 Pour les détails avec lignes, utiliser /sage/documents/get
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with sage._com_context(), sage._lock_com:
|
factures = sage.lister_toutes_factures_cache(filtre)
|
||||||
factory = sage.cial.FactoryDocumentVente
|
|
||||||
factures = []
|
|
||||||
index = 1
|
|
||||||
max_iterations = limit * 3
|
|
||||||
|
|
||||||
while len(factures) < limit and index < max_iterations:
|
if statut is not None:
|
||||||
try:
|
factures = [f for f in factures if f.get("statut") == statut]
|
||||||
persist = factory.List(index)
|
|
||||||
if persist is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
factures = factures[:limit]
|
||||||
doc.Read()
|
|
||||||
|
|
||||||
# Filtrer factures (type 60)
|
logger.info(f"✅ {len(factures)} factures retournées depuis le cache")
|
||||||
if getattr(doc, "DO_Type", -1) != settings.SAGE_TYPE_FACTURE:
|
|
||||||
index += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
doc_statut = getattr(doc, "DO_Statut", 0)
|
|
||||||
|
|
||||||
if statut is None or doc_statut == statut:
|
|
||||||
# Charger client
|
|
||||||
client_code = ""
|
|
||||||
client_intitule = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
client_obj = getattr(doc, "Client", None)
|
|
||||||
if client_obj:
|
|
||||||
client_obj.Read()
|
|
||||||
client_code = getattr(client_obj, "CT_Num", "").strip()
|
|
||||||
client_intitule = getattr(client_obj, "CT_Intitule", "").strip()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Charger les lignes
|
|
||||||
lignes = []
|
|
||||||
try:
|
|
||||||
factory_lignes = getattr(doc, "FactoryDocumentLigne", None)
|
|
||||||
if not factory_lignes:
|
|
||||||
factory_lignes = getattr(doc, "FactoryDocumentVenteLigne", None)
|
|
||||||
|
|
||||||
if factory_lignes:
|
|
||||||
ligne_index = 1
|
|
||||||
while ligne_index <= 100:
|
|
||||||
try:
|
|
||||||
ligne_persist = factory_lignes.List(ligne_index)
|
|
||||||
if ligne_persist is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
ligne = win32com.client.CastTo(ligne_persist, "IBODocumentLigne3")
|
|
||||||
ligne.Read()
|
|
||||||
|
|
||||||
# Charger référence article
|
|
||||||
article_ref = ""
|
|
||||||
try:
|
|
||||||
article_ref = getattr(ligne, "AR_Ref", "").strip()
|
|
||||||
if not article_ref:
|
|
||||||
article_obj = getattr(ligne, "Article", None)
|
|
||||||
if article_obj:
|
|
||||||
article_obj.Read()
|
|
||||||
article_ref = getattr(article_obj, "AR_Ref", "").strip()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
lignes.append({
|
|
||||||
"article": article_ref,
|
|
||||||
"designation": getattr(ligne, "DL_Design", ""),
|
|
||||||
"quantite": float(getattr(ligne, "DL_Qte", 0.0)),
|
|
||||||
"prix_unitaire": float(getattr(ligne, "DL_PrixUnitaire", 0.0)),
|
|
||||||
"montant_ht": float(getattr(ligne, "DL_MontantHT", 0.0))
|
|
||||||
})
|
|
||||||
|
|
||||||
ligne_index += 1
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Erreur ligne {ligne_index}: {e}")
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Erreur chargement lignes: {e}")
|
|
||||||
|
|
||||||
factures.append({
|
|
||||||
"numero": getattr(doc, "DO_Piece", ""),
|
|
||||||
"reference": getattr(doc, "DO_Ref", ""), # ✅ AJOUT
|
|
||||||
"date": str(getattr(doc, "DO_Date", "")),
|
|
||||||
"client_code": client_code,
|
|
||||||
"client_intitule": client_intitule,
|
|
||||||
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)),
|
|
||||||
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)),
|
|
||||||
"statut": doc_statut,
|
|
||||||
"lignes": lignes
|
|
||||||
})
|
|
||||||
|
|
||||||
index += 1
|
|
||||||
except:
|
|
||||||
index += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(f"✅ {len(factures)} factures retournées")
|
|
||||||
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}")
|
logger.error(f"❌ Erreur liste factures: {e}", exc_info=True)
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
@app.post("/sage/client/remise-max", dependencies=[Depends(verify_token)])
|
@app.post("/sage/client/remise-max", dependencies=[Depends(verify_token)])
|
||||||
|
|
@ -2670,15 +2369,16 @@ 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):
|
||||||
"""
|
"""
|
||||||
⚡ ENDPOINT DIRECT : Liste fournisseurs via FactoryFournisseur
|
⚡ Liste rapide des fournisseurs depuis le CACHE
|
||||||
|
|
||||||
✅ Utilise la méthode corrigée qui ne dépend pas du cache
|
✅ Utilise le cache mémoire pour une réponse instantanée
|
||||||
|
🔄 Cache actualisé automatiquement toutes les 15 minutes
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# ✅ Appel direct à la méthode corrigée
|
# ✅ Utiliser le cache au lieu de la lecture directe
|
||||||
fournisseurs = sage.lister_tous_fournisseurs(req.filtre)
|
fournisseurs = sage.lister_tous_fournisseurs_cache(req.filtre)
|
||||||
|
|
||||||
logger.info(f"✅ {len(fournisseurs)} fournisseurs retournés via endpoint")
|
logger.info(f"✅ {len(fournisseurs)} fournisseurs retournés depuis le cache")
|
||||||
|
|
||||||
return {"success": True, "data": fournisseurs}
|
return {"success": True, "data": fournisseurs}
|
||||||
|
|
||||||
|
|
@ -3022,6 +2722,68 @@ def modifier_facture_endpoint(req: FactureUpdateGatewayRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/documents/generate-pdf", dependencies=[Depends(verify_token)])
|
||||||
|
def generer_pdf_document(req: PDFGenerationRequest):
|
||||||
|
"""
|
||||||
|
📄 Génération PDF d'un document (endpoint généralisé)
|
||||||
|
|
||||||
|
**Supporte tous les types de documents Sage:**
|
||||||
|
- Devis (0)
|
||||||
|
- Bons de commande (10)
|
||||||
|
- Bons de livraison (30)
|
||||||
|
- Factures (60)
|
||||||
|
- Avoirs (50)
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. Charge le document depuis Sage
|
||||||
|
2. Génère le PDF via l'état Sage correspondant
|
||||||
|
3. Retourne le PDF en base64
|
||||||
|
|
||||||
|
Args:
|
||||||
|
req: Requête contenant doc_id et type_doc
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"pdf_base64": "JVBERi0xLjQK...",
|
||||||
|
"taille_octets": 12345,
|
||||||
|
"type_doc": 0,
|
||||||
|
"numero": "DE00001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
|
if not pdf_bytes:
|
||||||
|
raise HTTPException(500, "PDF vide généré")
|
||||||
|
|
||||||
|
# Encoder en base64 pour le transport JSON
|
||||||
|
import base64
|
||||||
|
pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
|
||||||
|
|
||||||
|
logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": {
|
||||||
|
"pdf_base64": pdf_base64,
|
||||||
|
"taille_octets": len(pdf_bytes),
|
||||||
|
"type_doc": req.type_doc,
|
||||||
|
"numero": req.doc_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur génération PDF: {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# LANCEMENT
|
# LANCEMENT
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue