diff --git a/api.py b/api.py index 0b37c3a..d1f7d8e 100644 --- a/api.py +++ b/api.py @@ -1252,6 +1252,88 @@ async def telecharger_devis_pdf(id: str): logger.error(f"Erreur génération PDF: {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"]) async def envoyer_devis_email( diff --git a/sage_client.py b/sage_client.py index 5f613c6..e45c6eb 100644 --- a/sage_client.py +++ b/sage_client.py @@ -543,6 +543,87 @@ class SageGatewayClient: "numero": numero, "facture_data": facture_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