diff --git a/api.py b/api.py index 1d9e4a5..9dc0bc2 100644 --- a/api.py +++ b/api.py @@ -979,46 +979,21 @@ def normaliser_type_doc(type_doc: int) -> int: return type_doc if type_doc == 0 else type_doc // 10 - @app.post("/signature/universign/send", tags=["Signatures"]) async def envoyer_signature_optimise( demande: SignatureRequest, session: AsyncSession = Depends(get_session) ): try: - # === DEBUG 1: Normalisation du type === - type_doc_normalise = normaliser_type_doc(demande.type_doc) - logger.info(f"🔍 DEBUG: type_doc original={demande.type_doc}, normalisé={type_doc_normalise}") - - # === DEBUG 2: Lecture document === - logger.info(f"🔍 DEBUG: Lecture document {demande.doc_id} (type={type_doc_normalise})") - - # Pour les devis, utiliser lire_devis qui est plus fiable - if demande.type_doc == 0: - doc = sage_client.lire_devis(demande.doc_id) - logger.info(f"🔍 DEBUG: lire_devis retourne: {type(doc)} - {doc}") - else: - doc = sage_client.lire_document(demande.doc_id, type_doc_normalise) - logger.info(f"🔍 DEBUG: lire_document retourne: {type(doc)} - {doc}") - + doc = sage_client.lire_document( + demande.doc_id, normaliser_type_doc(demande.type_doc) + ) if not doc: - logger.error(f"❌ Document {demande.doc_id} introuvable ou vide") raise HTTPException(404, f"Document {demande.doc_id} introuvable") - # === DEBUG 3: Vérification structure du document === - logger.info(f"🔍 DEBUG: Clés du document: {doc.keys() if isinstance(doc, dict) else 'PAS UN DICT!'}") - - # === DEBUG 4: Génération PDF === - logger.info(f"🔍 DEBUG: Génération PDF pour {demande.doc_id}") - - try: - # Utiliser le type original pour _generate_pdf - pdf_bytes = email_queue._generate_pdf(demande.doc_id, type_doc_normalise) - logger.info(f"✅ PDF généré: {len(pdf_bytes)} octets") - except Exception as pdf_error: - logger.error(f"❌ Erreur génération PDF: {pdf_error}", exc_info=True) - raise HTTPException(500, f"Erreur génération PDF: {str(pdf_error)}") + pdf_bytes = email_queue._generate_pdf( + demande.doc_id, normaliser_type_doc(demande.type_doc) + ) - # === Construction doc_data avec protection contre None === doc_data = { "type_doc": demande.type_doc, "type_label": { @@ -1028,13 +1003,10 @@ async def envoyer_signature_optimise( 60: "Facture", 50: "Avoir", }.get(demande.type_doc, "Document"), - "montant_ttc": doc.get("total_ttc", 0) if doc else 0, - "date": doc.get("date", datetime.now().strftime("%d/%m/%Y")) if doc else datetime.now().strftime("%d/%m/%Y"), + "montant_ttc": doc.get("total_ttc", 0), + "date": doc.get("date", datetime.now().strftime("%d/%m/%Y")), } - - logger.info(f"🔍 DEBUG: doc_data={doc_data}") - # === Appel Universign === resultat = await universign_envoyer( doc_id=demande.doc_id, pdf_bytes=pdf_bytes, @@ -1045,10 +1017,8 @@ async def envoyer_signature_optimise( ) if "error" in resultat: - logger.error(f"❌ Erreur Universign: {resultat['error']}") raise HTTPException(500, resultat["error"]) - # === Enregistrement en base === signature_log = SignatureLog( id=str(uuid.uuid4()), document_id=demande.doc_id, @@ -1068,7 +1038,9 @@ async def envoyer_signature_optimise( demande.doc_id, demande.type_doc, "UniversignID", resultat["transaction_id"] ) - logger.info(f"✅ Signature envoyée: {demande.doc_id} → {demande.email_signataire}") + logger.info( + f"Signature envoyée: {demande.doc_id} (Email: {resultat['email_sent']})" + ) return { "success": True, @@ -1082,9 +1054,9 @@ async def envoyer_signature_optimise( except HTTPException: raise except Exception as e: - logger.error(f"❌ Erreur inattendue signature: {e}", exc_info=True) - raise HTTPException(500, f"Erreur: {str(e)}") - + logger.error(f"Erreur signature: {e}") + raise HTTPException(500, str(e)) + @app.post("/webhooks/universign", tags=["Signatures"]) async def webhook_universign( diff --git a/email_queue.py b/email_queue.py index 94e975e..ba1b253 100644 --- a/email_queue.py +++ b/email_queue.py @@ -180,13 +180,13 @@ class EmailQueue: def _generate_pdf(self, doc_id: str, type_doc: int) -> bytes: if not self.sage_client: - logger.error(" sage_client non configuré") + logger.error("❌ sage_client non configuré") raise Exception("sage_client non disponible") try: doc = self.sage_client.lire_document(doc_id, type_doc) except Exception as e: - logger.error(f" Erreur récupération document {doc_id}: {e}") + logger.error(f"❌ Erreur récupération document {doc_id}: {e}") raise Exception(f"Document {doc_id} inaccessible") if not doc: @@ -218,11 +218,11 @@ class EmailQueue: y -= 0.8 * cm pdf.setFont("Helvetica", 11) - pdf.drawString(2 * cm, y, f"Code: {doc.get('client_code', '')}") + pdf.drawString(2 * cm, y, f"Code: {doc.get('client_code') or ''}") y -= 0.6 * cm - pdf.drawString(2 * cm, y, f"Nom: {doc.get('client_intitule', '')}") + pdf.drawString(2 * cm, y, f"Nom: {doc.get('client_intitule') or ''}") y -= 0.6 * cm - pdf.drawString(2 * cm, y, f"Date: {doc.get('date', '')}") + pdf.drawString(2 * cm, y, f"Date: {doc.get('date') or ''}") y -= 1.5 * cm pdf.setFont("Helvetica-Bold", 14) @@ -247,11 +247,20 @@ class EmailQueue: y = height - 3 * cm pdf.setFont("Helvetica", 9) - designation = ligne.get("designation", "")[:50] + designation = ( + ligne.get("designation") + or ligne.get("designation_article") + or "" + ) + if designation: + designation = str(designation)[:50] + else: + designation = "" + pdf.drawString(2 * cm, y, designation) - pdf.drawString(10 * cm, y, str(ligne.get("quantite", 0))) - pdf.drawString(12 * cm, y, f"{ligne.get('prix_unitaire', 0):.2f}€") - pdf.drawString(15 * cm, y, f"{ligne.get('montant_ht', 0):.2f}€") + pdf.drawString(10 * cm, y, str(ligne.get("quantite") or 0)) + pdf.drawString(12 * cm, y, f"{ligne.get('prix_unitaire_ht') or ligne.get('prix_unitaire', 0):.2f}€") + pdf.drawString(15 * cm, y, f"{ligne.get('montant_ligne_ht') or ligne.get('montant_ht', 0):.2f}€") y -= 0.6 * cm y -= 1 * cm @@ -260,17 +269,17 @@ class EmailQueue: y -= 0.8 * cm pdf.setFont("Helvetica-Bold", 11) pdf.drawString(12 * cm, y, "Total HT:") - pdf.drawString(15 * cm, y, f"{doc.get('total_ht', 0):.2f}€") + pdf.drawString(15 * cm, y, f"{doc.get('total_ht') or 0:.2f}€") y -= 0.6 * cm pdf.drawString(12 * cm, y, "TVA (20%):") - tva = doc.get("total_ttc", 0) - doc.get("total_ht", 0) + tva = (doc.get("total_ttc") or 0) - (doc.get("total_ht") or 0) pdf.drawString(15 * cm, y, f"{tva:.2f}€") y -= 0.6 * cm pdf.setFont("Helvetica-Bold", 14) pdf.drawString(12 * cm, y, "Total TTC:") - pdf.drawString(15 * cm, y, f"{doc.get('total_ttc', 0):.2f}€") + pdf.drawString(15 * cm, y, f"{doc.get('total_ttc') or 0:.2f}€") pdf.setFont("Helvetica", 8) pdf.drawString( @@ -281,9 +290,10 @@ class EmailQueue: pdf.save() buffer.seek(0) - logger.info(f" PDF généré: {doc_id}.pdf") + logger.info(f"✅ PDF généré: {doc_id}.pdf") return buffer.read() - + + def _send_smtp(self, msg): try: with smtplib.SMTP( diff --git a/sage_client.py b/sage_client.py index c0f89cd..0384be1 100644 --- a/sage_client.py +++ b/sage_client.py @@ -16,7 +16,7 @@ class SageGatewayClient: self.timeout = 30 def _post(self, endpoint: str, data: dict = None, retries: int = 3) -> dict: - """POST avec retry automatique et meilleur logging""" + """POST avec retry automatique""" import time for attempt in range(retries): @@ -28,22 +28,12 @@ class SageGatewayClient: timeout=self.timeout, ) r.raise_for_status() - - response_json = r.json() - - # DEBUG: Logger la réponse brute - logger.debug(f"🔍 Response {endpoint}: {response_json}") - - # Vérifier que c'est bien un dict - if not isinstance(response_json, dict): - logger.error(f"❌ Réponse inattendue (pas un dict): {type(response_json)}") - return {"success": False, "data": None, "error": "Réponse invalide"} - - return response_json - + return r.json() except requests.exceptions.RequestException as e: if attempt == retries - 1: - logger.error(f"❌ Échec après {retries} tentatives sur {endpoint}: {e}") + logger.error( + f" Échec après {retries} tentatives sur {endpoint}: {e}" + ) raise time.sleep(2**attempt) @@ -90,26 +80,8 @@ class SageGatewayClient: return self._post("/sage/devis/create", devis_data).get("data", {}) def lire_devis(self, numero: str) -> Optional[Dict]: - """Lecture d'un devis avec meilleure gestion d'erreur""" - try: - response = self._post("/sage/devis/get", {"code": numero}) - - logger.info(f"🔍 lire_devis({numero}) response keys: {response.keys() if response else 'None'}") - - if not response: - return None - - data = response.get("data") - - # Si data est None mais success=True, c'est suspect - if data is None and response.get("success"): - logger.warning(f"⚠️ success=True mais data=None pour devis {numero}") - - return data - - except Exception as e: - logger.error(f"❌ Erreur lire_devis {numero}: {e}") - return None + """Lecture d'un devis""" + return self._post("/sage/devis/get", {"code": numero}).get("data") def lister_devis( self, @@ -140,30 +112,10 @@ class SageGatewayClient: raise def lire_document(self, numero: str, type_doc: int) -> Optional[Dict]: - """Lecture d'un document générique avec meilleure gestion d'erreur""" - try: - response = self._post( - "/sage/documents/get", - {"numero": numero, "type_doc": type_doc} - ) - - # Debug - logger.info(f"🔍 lire_document({numero}, {type_doc}) response: {response}") - - if not response: - logger.warning(f"⚠️ Réponse vide pour document {numero}") - return None - - data = response.get("data") - - if data is None: - logger.warning(f"⚠️ data=None pour document {numero}, response={response}") - - return data - - except Exception as e: - logger.error(f"❌ Erreur lire_document {numero}: {e}") - return None + """Lecture d'un document générique""" + return self._post( + "/sage/documents/get", {"numero": numero, "type_doc": type_doc} + ).get("data") def transformer_document( self, numero_source: str, type_source: int, type_cible: int