diff --git a/api.py b/api.py index 237d719..e8e8f82 100644 --- a/api.py +++ b/api.py @@ -1190,21 +1190,21 @@ templates_signature_email = { """, - "variables_disponibles": [ - "NOM_SIGNATAIRE", - "TYPE_DOC", - "NUMERO", - "DATE", - "MONTANT_TTC", - "SIGNER_URL", - "CONTACT_EMAIL", - ], - }, - "signature_confirmee": { - "id": "signature_confirmee", - "nom": "Confirmation de Signature", - "sujet": "✅ Document signé - {{TYPE_DOC}} {{NUMERO}}", - "corps_html": """ + "variables_disponibles": [ + "NOM_SIGNATAIRE", + "TYPE_DOC", + "NUMERO", + "DATE", + "MONTANT_TTC", + "SIGNER_URL", + "CONTACT_EMAIL", + ], + }, + "signature_confirmee": { + "id": "signature_confirmee", + "nom": "Confirmation de Signature", + "sujet": "✅ Document signé - {{TYPE_DOC}} {{NUMERO}}", + "corps_html": """ @@ -1301,20 +1301,20 @@ templates_signature_email = { """, - "variables_disponibles": [ - "NOM_SIGNATAIRE", - "TYPE_DOC", - "NUMERO", - "DATE_SIGNATURE", - "TRANSACTION_ID", - "CONTACT_EMAIL", - ], - }, - "relance_signature": { - "id": "relance_signature", - "nom": "Relance Signature en Attente", - "sujet": "⏰ Rappel - Signature en attente {{TYPE_DOC}} {{NUMERO}}", - "corps_html": """ + "variables_disponibles": [ + "NOM_SIGNATAIRE", + "TYPE_DOC", + "NUMERO", + "DATE_SIGNATURE", + "TRANSACTION_ID", + "CONTACT_EMAIL", + ], + }, + "relance_signature": { + "id": "relance_signature", + "nom": "Relance Signature en Attente", + "sujet": "⏰ Rappel - Signature en attente {{TYPE_DOC}} {{NUMERO}}", + "corps_html": """ @@ -1431,240 +1431,170 @@ async def universign_envoyer_avec_email( auth = (api_key, "") logger.info(f"🔐 Démarrage processus Universign pour {email}") - logger.info(f"📌 API URL: {api_url}") - logger.info(f"📌 Document: {doc_id} - Type: {doc_data.get('type_label')}") - + logger.info(f"📌 Document: {doc_id} ({doc_data.get('type_label')})") + # ======================================== - # VÉRIFICATION PRÉLIMINAIRE : PDF valide ? + # VÉRIFICATION : PDF valide ? # ======================================== if not pdf_bytes or len(pdf_bytes) == 0: - logger.error(f"❌ PDF vide ou None") - raise Exception("Le PDF généré est vide. Vérifiez la génération du PDF.") - - logger.info(f"✅ PDF reçu : {len(pdf_bytes)} octets") + raise Exception("Le PDF généré est vide") + + logger.info(f"✅ PDF valide : {len(pdf_bytes)} octets") # ======================================== # ÉTAPE 1 : Créer la transaction # ======================================== logger.info(f"📝 ÉTAPE 1/7 : Création transaction") - - transaction_payload = { - "name": f"{doc_data.get('type_label', 'Document')} {doc_id}", - "language": "fr", - } - + response = requests.post( f"{api_url}/transactions", auth=auth, - json=transaction_payload, + json={ + "name": f"{doc_data.get('type_label', 'Document')} {doc_id}", + "language": "fr", + }, timeout=30, ) - - logger.info(f"Status création transaction: {response.status_code}") - + if response.status_code != 200: - logger.error(f"❌ Réponse: {response.text}") - raise Exception(f"Erreur création transaction: {response.status_code} - {response.text}") - - transaction_data = response.json() - transaction_id = transaction_data.get("id") - - if not transaction_id: - logger.error(f"❌ Pas de transaction_id dans la réponse: {transaction_data}") - raise Exception("Transaction créée mais ID manquant") - + logger.error(f"❌ Erreur création transaction: {response.text}") + raise Exception(f"Erreur création transaction: {response.status_code}") + + transaction_id = response.json().get("id") logger.info(f"✅ Transaction créée: {transaction_id}") # ======================================== # ÉTAPE 2 : Upload du fichier PDF # ======================================== logger.info(f"📄 ÉTAPE 2/7 : Upload PDF") - - # Préparer le fichier multipart - filename = f"{doc_data.get('type_label', 'Document')}_{doc_id}.pdf" + files = { - "file": (filename, pdf_bytes, "application/pdf") + "file": ( + f"{doc_data.get('type_label', 'Document')}_{doc_id}.pdf", + pdf_bytes, + "application/pdf", + ) } - - logger.debug(f"Upload fichier: {filename}, taille: {len(pdf_bytes)} octets") - + response = requests.post( - f"{api_url}/files", - auth=auth, - files=files, - timeout=60, # Plus de timeout pour gros fichiers + f"{api_url}/files", + auth=auth, + files=files, + timeout=60, ) - - logger.info(f"Status upload fichier: {response.status_code}") - + if response.status_code not in [200, 201]: logger.error(f"❌ Erreur upload: {response.text}") - raise Exception(f"Erreur upload fichier: {response.status_code} - {response.text}") - - # Parser la réponse - try: - file_data = response.json() - logger.debug(f"Réponse upload: {file_data}") - except Exception as e: - logger.error(f"❌ Erreur parsing réponse upload: {e}") - logger.error(f"Réponse brute: {response.text[:500]}") - raise Exception(f"Réponse upload invalide: {e}") - - file_id = file_data.get("id") - + raise Exception(f"Erreur upload fichier: {response.status_code}") + + file_id = response.json().get("id") + if not file_id: - logger.error(f"❌ Pas de file_id dans la réponse") - logger.error(f"Réponse complète: {file_data}") + logger.error(f"❌ Pas de file_id retourné") raise Exception("Upload réussi mais file_id manquant") - + logger.info(f"✅ Fichier uploadé: {file_id}") # ======================================== - # ÉTAPE 3 : Ajouter le document à la transaction + # ÉTAPE 3 : Ajouter le document (form-data) # ======================================== - logger.info(f"📋 ÉTAPE 3/7 : Ajout document à transaction {transaction_id}") - - document_payload = { - "document": file_id # ✅ Utiliser "document" pas "file" - } - - logger.debug(f"Payload document: {document_payload}") - logger.debug(f"URL: {api_url}/transactions/{transaction_id}/documents") - + logger.info(f"📋 ÉTAPE 3/7 : Ajout document à transaction") + response = requests.post( f"{api_url}/transactions/{transaction_id}/documents", auth=auth, - json=document_payload, + data={"document": file_id}, # ✅ UTILISER data= (form-data) timeout=30, ) - - logger.info(f"Status ajout document: {response.status_code}") - + if response.status_code not in [200, 201]: logger.error(f"❌ Erreur ajout document: {response.text}") - logger.error(f"Payload envoyé: {document_payload}") - logger.error(f"file_id utilisé: '{file_id}' (type: {type(file_id)})") - raise Exception(f"Erreur ajout document: {response.status_code} - {response.text}") - - try: - document_data = response.json() - logger.debug(f"Réponse ajout document: {document_data}") - except Exception as e: - logger.error(f"❌ Erreur parsing réponse document: {e}") - raise - - document_id = document_data.get("id") - - if not document_id: - logger.error(f"❌ Pas de document_id dans la réponse") - logger.error(f"Réponse complète: {document_data}") - raise Exception("Document ajouté mais ID manquant") - + raise Exception(f"Erreur ajout document: {response.status_code}") + + document_id = response.json().get("id") logger.info(f"✅ Document ajouté: {document_id}") # ======================================== # ÉTAPE 4 : Créer le signataire # ======================================== logger.info(f"👤 ÉTAPE 4/7 : Création signataire") - + nom_parts = nom.split() first_name = nom_parts[0] if len(nom_parts) > 0 else nom last_name = " ".join(nom_parts[1:]) if len(nom_parts) > 1 else "" - - signer_payload = { - "email": email, - "firstName": first_name, - "lastName": last_name, - } - - logger.debug(f"Payload signataire: {signer_payload}") - + response = requests.post( f"{api_url}/transactions/{transaction_id}/signers", auth=auth, - json=signer_payload, + data={ # ✅ UTILISER data= (form-data) + "email": email, + "firstName": first_name, + "lastName": last_name, + }, timeout=30, ) - - logger.info(f"Status création signataire: {response.status_code}") - + if response.status_code not in [200, 201]: logger.error(f"❌ Erreur création signataire: {response.text}") - raise Exception(f"Erreur création signataire: {response.status_code} - {response.text}") - - signer_data = response.json() - signer_id = signer_data.get("id") - - if not signer_id: - logger.error(f"❌ Pas de signer_id dans la réponse") - raise Exception("Signataire créé mais ID manquant") - + raise Exception(f"Erreur création signataire: {response.status_code}") + + signer_id = response.json().get("id") logger.info(f"✅ Signataire créé: {signer_id}") # ======================================== # ÉTAPE 5 : Créer le champ de signature # ======================================== logger.info(f"✍️ ÉTAPE 5/7 : Création champ signature") - - field_payload = { - "type": "signature", - "page": 1, - "signer": signer_id, - } - - logger.debug(f"Payload champ: {field_payload}") - + response = requests.post( f"{api_url}/transactions/{transaction_id}/documents/{document_id}/fields", auth=auth, - json=field_payload, + data={ # ✅ UTILISER data= (form-data) + "type": "signature", + "page": "1", + "signer": signer_id, + }, timeout=30, ) - - logger.info(f"Status création champ: {response.status_code}") - + if response.status_code not in [200, 201]: logger.error(f"❌ Erreur création champ: {response.text}") - raise Exception(f"Erreur création champ: {response.status_code} - {response.text}") - - field_data = response.json() - field_id = field_data.get("id") - + raise Exception(f"Erreur création champ: {response.status_code}") + + field_id = response.json().get("id") logger.info(f"✅ Champ créé: {field_id}") # ======================================== # ÉTAPE 6 : Démarrer la transaction # ======================================== logger.info(f"🚀 ÉTAPE 6/7 : Démarrage transaction") - + response = requests.post( - f"{api_url}/transactions/{transaction_id}/start", - auth=auth, - json={}, - timeout=30 + f"{api_url}/transactions/{transaction_id}/start", auth=auth, timeout=30 ) - - logger.info(f"Status démarrage: {response.status_code}") - + if response.status_code not in [200, 201]: logger.error(f"❌ Erreur démarrage: {response.text}") - raise Exception(f"Erreur démarrage: {response.status_code} - {response.text}") - + raise Exception(f"Erreur démarrage: {response.status_code}") + final_data = response.json() - + logger.info(f"✅ Transaction démarrée") + # ======================================== # ÉTAPE 7 : Récupérer l'URL de signature # ======================================== logger.info(f"🔗 ÉTAPE 7/7 : Récupération URL") - + signer_url = "" - + + # Chercher dans la réponse du /start if final_data.get("signers"): for signer in final_data["signers"]: if signer.get("email") == email: signer_url = signer.get("url", "") break - + + # Si pas trouvée, interroger le signataire directement if not signer_url: response = requests.get( f"{api_url}/transactions/{transaction_id}/signers/{signer_id}", @@ -1676,16 +1606,24 @@ async def universign_envoyer_avec_email( if not signer_url: logger.error(f"❌ URL de signature introuvable") - raise ValueError("URL de signature non retournée") + raise ValueError("URL de signature non retournée par Universign") - logger.info(f"✅ URL récupérée") + logger.info(f"✅ URL récupérée avec succès") # ======================================== # Créer l'email de notification # ======================================== + logger.info(f"📧 Préparation email de notification") + template = templates_signature_email["demande_signature"] - type_labels = {0: "Devis", 10: "Commande", 30: "Bon de Livraison", 60: "Facture", 50: "Avoir"} + type_labels = { + 0: "Devis", + 10: "Commande", + 30: "Bon de Livraison", + 60: "Facture", + 50: "Avoir", + } variables = { "NOM_SIGNATAIRE": nom, @@ -1704,6 +1642,7 @@ async def universign_envoyer_avec_email( sujet = sujet.replace(f"{{{{{var}}}}}", str(valeur)) corps = corps.replace(f"{{{{{var}}}}}", str(valeur)) + # Créer log email email_log = EmailLog( id=str(uuid.uuid4()), destinataire=email, @@ -1718,9 +1657,12 @@ async def universign_envoyer_avec_email( session.add(email_log) await session.flush() + + # Enqueue l'email email_queue.enqueue(email_log.id) - logger.info(f"✅ Processus terminé avec succès") + logger.info(f"✅ Email mis en file pour {email}") + logger.info(f"🎉 Processus Universign terminé avec succès") return { "transaction_id": transaction_id, @@ -1730,10 +1672,25 @@ async def universign_envoyer_avec_email( "email_sent": True, } + except requests.exceptions.HTTPError as e: + logger.error(f"❌ Erreur HTTP Universign: {e}") + if e.response: + logger.error(f"Status: {e.response.status_code}") + logger.error(f"Body: {e.response.text}") + return { + "error": f"Erreur Universign: {e.response.status_code if e.response else 'Unknown'} - {e.response.text if e.response else str(e)}", + "statut": "ERREUR", + "email_sent": False, + } except Exception as e: logger.error(f"❌ Erreur Universign: {e}", exc_info=True) - return {"error": str(e), "statut": "ERREUR", "email_sent": False} - + return { + "error": str(e), + "statut": "ERREUR", + "email_sent": False, + } + + async def universign_statut(transaction_id: str) -> Dict: """Récupération statut signature""" import requests