diff --git a/api.py b/api.py index c00a411..237d719 100644 --- a/api.py +++ b/api.py @@ -1430,182 +1430,262 @@ async def universign_envoyer_avec_email( api_url = settings.universign_api_url 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')}") + + # ======================================== + # VÉRIFICATION PRÉLIMINAIRE : 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") + # ======================================== # ÉTAPE 1 : CrĂ©er la transaction # ======================================== - logger.info(f"🔐 CrĂ©ation transaction Universign pour {email}") + 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={ - "name": f"{doc_data.get('type_label', 'Document')} {doc_id}", - "language": "fr", - "profile": "default", # ✅ Ajout du profil - }, + json=transaction_payload, timeout=30, ) + logger.info(f"Status crĂ©ation transaction: {response.status_code}") + if response.status_code != 200: - logger.error(f"❌ Erreur crĂ©ation transaction: {response.status_code} - {response.text}") - raise Exception(f"Erreur crĂ©ation transaction: {response.status_code}") + logger.error(f"❌ RĂ©ponse: {response.text}") + raise Exception(f"Erreur crĂ©ation transaction: {response.status_code} - {response.text}") - response.raise_for_status() - transaction_id = response.json().get("id") - + 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.info(f"✅ Transaction créée: {transaction_id}") # ======================================== # ÉTAPE 2 : Upload du fichier PDF # ======================================== - logger.info(f"📄 Upload PDF ({len(pdf_bytes)} octets)") + 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": ( - f"{doc_data.get('type_label', 'Document')}_{doc_id}.pdf", - pdf_bytes, - "application/pdf", - ) + "file": (filename, 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=30 + timeout=60, # Plus de timeout pour gros fichiers ) - if response.status_code != 200: - logger.error(f"❌ Erreur upload fichier: {response.status_code} - {response.text}") - raise Exception(f"Erreur upload fichier: {response.status_code}") - - response.raise_for_status() - file_id = response.json().get("id") - + 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") + + if not file_id: + logger.error(f"❌ Pas de file_id dans la rĂ©ponse") + logger.error(f"RĂ©ponse complĂšte: {file_data}") + raise Exception("Upload rĂ©ussi mais file_id manquant") + logger.info(f"✅ Fichier uploadĂ©: {file_id}") # ======================================== - # ÉTAPE 3 : CrĂ©er le document dans la transaction + # ÉTAPE 3 : Ajouter le document Ă  la transaction # ======================================== - logger.info(f"📋 Ajout document Ă  la transaction") + 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") response = requests.post( f"{api_url}/transactions/{transaction_id}/documents", auth=auth, - json={"document": file_id}, # ✅ Utiliser 'file' au lieu de 'document' + json=document_payload, timeout=30, ) - if response.status_code != 200: - logger.error(f"❌ Erreur ajout document: {response.status_code} - {response.text}") - raise Exception(f"Erreur ajout document: {response.status_code}") - - response.raise_for_status() - document_id = response.json().get("id") - + 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") + logger.info(f"✅ Document ajoutĂ©: {document_id}") # ======================================== - # ÉTAPE 4 : Ajouter un champ de signature + # ÉTAPE 4 : CrĂ©er le signataire # ======================================== - logger.info(f"✍ Ajout champ signature") + logger.info(f"đŸ‘€ ÉTAPE 4/7 : CrĂ©ation signataire") - response = requests.post( - f"{api_url}/transactions/{transaction_id}/documents/{document_id}/fields", - auth=auth, - json={ - "type": "signature", - "page": 1, # ✅ PrĂ©ciser la page - # Position optionnelle - Universign peut la placer automatiquement - # "x": 100, - # "y": 600, - }, - timeout=30, - ) + 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 "" - if response.status_code != 200: - logger.error(f"❌ Erreur ajout champ: {response.status_code} - {response.text}") - raise Exception(f"Erreur ajout champ signature: {response.status_code}") - - response.raise_for_status() - field_id = response.json().get("id") - - logger.info(f"✅ Champ signature créé: {field_id}") - - # ======================================== - # ÉTAPE 5 : Ajouter le signataire - # ======================================== - logger.info(f"đŸ‘€ Ajout signataire: {nom} ({email})") + 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={ - "email": email, - "firstName": nom.split()[0] if ' ' in nom else nom, # ✅ PrĂ©nom - "lastName": nom.split()[-1] if ' ' in nom else "", # ✅ Nom - # ✅ Lier le signataire au champ de signature - "fields": [field_id], - }, + json=signer_payload, timeout=30, ) - if response.status_code != 200: - logger.error(f"❌ Erreur ajout signataire: {response.status_code} - {response.text}") - raise Exception(f"Erreur ajout signataire: {response.status_code}") - - response.raise_for_status() - signer_id = response.json().get("id") + 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") + + logger.info(f"✅ Signataire créé: {signer_id}") - logger.info(f"✅ Signataire ajoutĂ©: {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, + 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") + + logger.info(f"✅ Champ créé: {field_id}") # ======================================== # ÉTAPE 6 : DĂ©marrer la transaction # ======================================== - logger.info(f"🚀 DĂ©marrage de la transaction") + logger.info(f"🚀 ÉTAPE 6/7 : DĂ©marrage transaction") response = requests.post( f"{api_url}/transactions/{transaction_id}/start", auth=auth, - json={}, # ✅ Body vide mais prĂ©sent + json={}, timeout=30 ) - if response.status_code != 200: - logger.error(f"❌ Erreur dĂ©marrage transaction: {response.status_code}") - logger.error(f"RĂ©ponse complĂšte: {response.text}") - raise Exception(f"Erreur dĂ©marrage transaction: {response.status_code} - {response.text}") - - response.raise_for_status() + 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}") + final_data = response.json() # ======================================== # ÉTAPE 7 : RĂ©cupĂ©rer l'URL de signature # ======================================== + logger.info(f"🔗 ÉTAPE 7/7 : RĂ©cupĂ©ration URL") + signer_url = "" + if final_data.get("signers"): for signer in final_data["signers"]: if signer.get("email") == email: signer_url = signer.get("url", "") break + + if not signer_url: + response = requests.get( + f"{api_url}/transactions/{transaction_id}/signers/{signer_id}", + auth=auth, + timeout=10, + ) + if response.status_code == 200: + signer_url = response.json().get("url", "") if not signer_url: - logger.warning("⚠ URL de signature non trouvĂ©e dans la rĂ©ponse") - raise ValueError("URL de signature non retournĂ©e par Universign") + logger.error(f"❌ URL de signature introuvable") + raise ValueError("URL de signature non retournĂ©e") - logger.info(f"✅ Signature Universign prĂȘte: {transaction_id}") + logger.info(f"✅ URL rĂ©cupĂ©rĂ©e") # ======================================== - # ÉTAPE 8 : CrĂ©er l'email de notification + # CrĂ©er l'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, @@ -1624,7 +1704,6 @@ 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, @@ -1639,11 +1718,9 @@ 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"📧 Email de signature envoyĂ© en file: {email}") + logger.info(f"✅ Processus terminĂ© avec succĂšs") return { "transaction_id": transaction_id, @@ -1653,18 +1730,10 @@ async def universign_envoyer_avec_email( "email_sent": True, } - except requests.exceptions.HTTPError as e: - logger.error(f"❌ Erreur HTTP Universign: {e}") - logger.error(f"RĂ©ponse: {e.response.text if e.response else 'N/A'}") - return { - "error": f"Erreur Universign: {e.response.status_code} - {e.response.text if e.response else str(e)}", - "statut": "ERREUR", - "email_sent": False, - } except Exception as e: - logger.error(f"❌ Erreur Universign+Email: {e}", exc_info=True) + logger.error(f"❌ Erreur Universign: {e}", exc_info=True) return {"error": str(e), "statut": "ERREUR", "email_sent": False} - + async def universign_statut(transaction_id: str) -> Dict: """RĂ©cupĂ©ration statut signature""" import requests