feat(documents): add generic PDF download endpoint for documents
This commit is contained in:
parent
14b2758b68
commit
a1794ac90f
2 changed files with 163 additions and 0 deletions
82
api.py
82
api.py
|
|
@ -1252,6 +1252,88 @@ async def telecharger_devis_pdf(id: str):
|
||||||
logger.error(f"Erreur génération PDF: {e}")
|
logger.error(f"Erreur génération PDF: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
@app.get("/documents/{type_doc}/{numero}/pdf", tags=["Documents"])
|
||||||
|
async def telecharger_document_pdf(
|
||||||
|
type_doc: int = Query(..., description="Type de document (0=Devis, 10=Commande, 30=Livraison, 60=Facture, 50=Avoir)"),
|
||||||
|
numero: str = Query(..., description="Numéro du document")
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
📄 Téléchargement PDF d'un document (route généralisée)
|
||||||
|
|
||||||
|
**Types de documents supportés:**
|
||||||
|
- `0`: Devis
|
||||||
|
- `10`: Bon de commande
|
||||||
|
- `30`: Bon de livraison
|
||||||
|
- `60`: Facture
|
||||||
|
- `50`: Bon d'avoir
|
||||||
|
|
||||||
|
**Exemple d'utilisation:**
|
||||||
|
- `GET /documents/0/DE00001/pdf` → PDF du devis DE00001
|
||||||
|
- `GET /documents/60/FA00001/pdf` → PDF de la facture FA00001
|
||||||
|
|
||||||
|
**Retour:**
|
||||||
|
- Fichier PDF prêt à télécharger
|
||||||
|
- Nom de fichier formaté selon le type de document
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type_doc: Type de document Sage (0-60)
|
||||||
|
numero: Numéro du document
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StreamingResponse avec le PDF
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Mapping des types vers les libellés
|
||||||
|
types_labels = {
|
||||||
|
0: "Devis",
|
||||||
|
10: "Commande",
|
||||||
|
20: "Preparation",
|
||||||
|
30: "BonLivraison",
|
||||||
|
40: "BonRetour",
|
||||||
|
50: "Avoir",
|
||||||
|
60: "Facture"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vérifier que le type est valide
|
||||||
|
if type_doc not in types_labels:
|
||||||
|
raise HTTPException(
|
||||||
|
400,
|
||||||
|
f"Type de document invalide: {type_doc}. "
|
||||||
|
f"Types valides: {list(types_labels.keys())}"
|
||||||
|
)
|
||||||
|
|
||||||
|
label = types_labels[type_doc]
|
||||||
|
|
||||||
|
logger.info(f"📄 Génération PDF: {label} {numero} (type={type_doc})")
|
||||||
|
|
||||||
|
# Appel à sage_client pour générer le PDF
|
||||||
|
pdf_bytes = sage_client.generer_pdf_document(numero, type_doc)
|
||||||
|
|
||||||
|
if not pdf_bytes:
|
||||||
|
raise HTTPException(
|
||||||
|
500,
|
||||||
|
f"Le PDF du document {numero} est vide"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets")
|
||||||
|
|
||||||
|
# Nom de fichier formaté
|
||||||
|
filename = f"{label}_{numero}.pdf"
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
iter([pdf_bytes]),
|
||||||
|
media_type="application/pdf",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f"attachment; filename={filename}",
|
||||||
|
"Content-Length": str(len(pdf_bytes))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur génération PDF {numero} (type {type_doc}): {e}", exc_info=True)
|
||||||
|
raise HTTPException(500, f"Erreur génération PDF: {str(e)}")
|
||||||
|
|
||||||
@app.post("/devis/{id}/envoyer", tags=["Devis"])
|
@app.post("/devis/{id}/envoyer", tags=["Devis"])
|
||||||
async def envoyer_devis_email(
|
async def envoyer_devis_email(
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,87 @@ class SageGatewayClient:
|
||||||
"numero": numero,
|
"numero": numero,
|
||||||
"facture_data": facture_data
|
"facture_data": facture_data
|
||||||
}).get("data", {})
|
}).get("data", {})
|
||||||
|
|
||||||
|
def generer_pdf_document(self, doc_id: str, type_doc: int) -> bytes:
|
||||||
|
"""
|
||||||
|
🆕 Génère le PDF d'un document via la gateway Windows (route généralisée)
|
||||||
|
|
||||||
|
**Cette méthode remplace les appels spécifiques par type de document**
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc_id: Numéro du document (ex: "DE00001", "FA00001")
|
||||||
|
type_doc: Type de document Sage:
|
||||||
|
- 0: Devis
|
||||||
|
- 10: Bon de commande
|
||||||
|
- 30: Bon de livraison
|
||||||
|
- 60: Facture
|
||||||
|
- 50: Bon d'avoir
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bytes: Contenu du PDF (binaire)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Si le PDF retourné est vide
|
||||||
|
RuntimeError: Si erreur de communication avec la gateway
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> pdf_bytes = sage_client.generer_pdf_document("DE00001", 0)
|
||||||
|
>>> with open("devis.pdf", "wb") as f:
|
||||||
|
... f.write(pdf_bytes)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"📄 Demande génération PDF: doc_id={doc_id}, type={type_doc}")
|
||||||
|
|
||||||
|
# Appel HTTP vers la gateway Windows
|
||||||
|
r = requests.post(
|
||||||
|
f"{self.url}/sage/documents/generate-pdf",
|
||||||
|
json={
|
||||||
|
"doc_id": doc_id,
|
||||||
|
"type_doc": type_doc
|
||||||
|
},
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=60 # Timeout élevé pour génération PDF
|
||||||
|
)
|
||||||
|
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
response_data = r.json()
|
||||||
|
|
||||||
|
# Vérifier que la réponse contient bien le PDF
|
||||||
|
if not response_data.get("success"):
|
||||||
|
error_msg = response_data.get("error", "Erreur inconnue")
|
||||||
|
raise RuntimeError(f"Gateway a retourné une erreur: {error_msg}")
|
||||||
|
|
||||||
|
pdf_base64 = response_data.get("data", {}).get("pdf_base64", "")
|
||||||
|
|
||||||
|
if not pdf_base64:
|
||||||
|
raise ValueError(
|
||||||
|
f"PDF vide retourné par la gateway pour {doc_id} (type {type_doc})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Décoder le base64
|
||||||
|
pdf_bytes = base64.b64decode(pdf_base64)
|
||||||
|
|
||||||
|
logger.info(f"✅ PDF décodé: {len(pdf_bytes)} octets")
|
||||||
|
|
||||||
|
return pdf_bytes
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
logger.error(f"⏱️ Timeout génération PDF pour {doc_id}")
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Timeout lors de la génération du PDF (>60s). "
|
||||||
|
f"Le document {doc_id} est peut-être trop volumineux."
|
||||||
|
)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"❌ Erreur HTTP génération PDF: {e}")
|
||||||
|
raise RuntimeError(f"Erreur de communication avec la gateway: {str(e)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur génération PDF: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue