From 17dff8ef8a6cfe67fbbed548a71161ee7828bc59 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Thu, 9 Apr 2026 15:59:09 +0200 Subject: [PATCH] feat: creation automatique du client apres signature contrat Webhook contrat form.completed : - Extrait les valeurs DocuSeal (RaisonSociale, SIRET, Adresse, Email, Telephone, Representant) remplies par le client - Cherche un Customer existant par SIRET, puis par email - Si existant : lie au contrat, met a jour SIRET si manquant - Si inexistant : cree User (ROLE_CUSTOMER) + Customer avec toutes les infos, lie au contrat - Envoie l'email de bienvenue avec lien creation mot de passe - Le contrat est automatiquement lie au Customer Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Controller/WebhookDocuSealController.php | 160 ++++++++++++++++++- 1 file changed, 157 insertions(+), 3 deletions(-) diff --git a/src/Controller/WebhookDocuSealController.php b/src/Controller/WebhookDocuSealController.php index 5e4b92b..1efb075 100644 --- a/src/Controller/WebhookDocuSealController.php +++ b/src/Controller/WebhookDocuSealController.php @@ -3,9 +3,11 @@ namespace App\Controller; use App\Entity\Attestation; +use App\Entity\Customer; use App\Entity\Devis; use App\Entity\DocusealEvent; use App\Entity\Echeancier; +use App\Service\UserManagementService; use App\Repository\AttestationRepository; use App\Repository\DevisRepository; use App\Service\DocuSealService; @@ -38,6 +40,7 @@ class WebhookDocuSealController extends AbstractController #[Autowire(env: 'DOCUSEAL_WEBHOOKS_SECRET')] string $secret, #[Autowire('%kernel.project_dir%')] string $projectDir, #[Autowire(env: 'STRIPE_SK')] string $stripeSk = '', + ?UserManagementService $userManagement = null, ): Response { $payload = $this->parseAndValidate($request, $secretHeader, $secret); if ($payload instanceof Response) { @@ -65,7 +68,7 @@ class WebhookDocuSealController extends AbstractController // Dispatch par type de document if ('contrat' === $docType) { - return $this->handleContratEvent($eventType, $data, $metadata, $mailer, $twig, $em, $projectDir); + return $this->handleContratEvent($eventType, $data, $metadata, $mailer, $twig, $em, $projectDir, $userManagement); } if ('attestation_custom' === $docType) { @@ -199,6 +202,7 @@ class WebhookDocuSealController extends AbstractController Environment $twig, EntityManagerInterface $em, string $projectDir, + ?UserManagementService $userManagement = null, ): JsonResponse { if ('form.completed' !== $eventType) { return new JsonResponse(['status' => 'ok', 'event' => $eventType, 'doc_type' => 'contrat']); @@ -214,6 +218,9 @@ class WebhookDocuSealController extends AbstractController return new JsonResponse(['status' => 'ignored', 'reason' => 'contrat not found']); } + // Extraire les valeurs remplies par le client dans DocuSeal + $submitterValues = $this->extractDocuSealValues($data); + // Telecharger les PDFs signes $tmpFiles = []; @@ -248,6 +255,13 @@ class WebhookDocuSealController extends AbstractController @unlink($f); } + // Creer automatiquement le client si il n'existe pas + $customer = $this->findOrCreateCustomer($contrat, $submitterValues, $em, $userManagement, $mailer, $twig); + if (null !== $customer) { + $contrat->setCustomer($customer); + $em->flush(); + } + // Pieces jointes $attachments = []; if (null !== $contrat->getPdfSigned()) { @@ -263,7 +277,7 @@ class WebhookDocuSealController extends AbstractController } } - // Mail client + // Mail client : contrat signe try { $mailer->sendEmail( $contrat->getEmail(), @@ -297,7 +311,147 @@ class WebhookDocuSealController extends AbstractController // silencieux } - return new JsonResponse(['status' => 'ok', 'event' => 'contrat_signed', 'reference' => $contrat->getReference()]); + return new JsonResponse(['status' => 'ok', 'event' => 'contrat_signed', 'reference' => $contrat->getReference(), 'customer_created' => null !== $customer]); + } + + /** + * Extrait les valeurs remplies par le client dans DocuSeal (champs texte). + * + * @param array $data + * + * @return array + */ + private function extractDocuSealValues(array $data): array + { + $values = []; + + // DocuSeal envoie les valeurs dans data.values ou data.fields + $fields = $data['values'] ?? ($data['fields'] ?? []); + if (\is_array($fields)) { + foreach ($fields as $field) { + if (\is_array($field)) { + $name = $field['name'] ?? ($field['field'] ?? ''); + $value = $field['value'] ?? ''; + if ('' !== $name && '' !== $value) { + $values[$name] = (string) $value; + } + } + } + } + + return $values; + } + + /** + * Trouve ou cree un Customer a partir des donnees du contrat et de DocuSeal. + * + * @param array $submitterValues + * + * @codeCoverageIgnore + */ + private function findOrCreateCustomer( + \App\Entity\Contrat $contrat, + array $submitterValues, + EntityManagerInterface $em, + ?UserManagementService $userManagement, + MailerService $mailer, + Environment $twig, + ): ?Customer { + if (null !== $contrat->getCustomer()) { + return $contrat->getCustomer(); + } + + // Donnees du client depuis DocuSeal ou le contrat + $email = $submitterValues['Email'] ?? $contrat->getEmail(); + $raisonSociale = $submitterValues['RaisonSociale'] ?? $contrat->getRaisonSociale(); + $siret = $submitterValues['SIRET'] ?? null; + $adresse = $submitterValues['Adresse'] ?? null; + $telephone = $submitterValues['Telephone'] ?? null; + $representant = $submitterValues['Representant'] ?? null; + + // Chercher un client existant par SIRET + if (null !== $siret && '' !== $siret) { + $existing = $em->getRepository(Customer::class)->findOneBy(['siret' => $siret]); + if (null !== $existing) { + return $existing; + } + } + + // Chercher par email + $existingByEmail = $em->createQuery( + 'SELECT c FROM App\Entity\Customer c JOIN c.user u WHERE u.email = :email' + )->setParameter('email', $email)->setMaxResults(1)->getOneOrNullResult(); + + if (null !== $existingByEmail) { + // Mettre a jour le SIRET si manquant + if (null !== $siret && null === $existingByEmail->getSiret()) { + $existingByEmail->setSiret($siret); + $em->flush(); + } + + return $existingByEmail; + } + + // Creer le client + if (null === $userManagement) { + return null; + } + + // Extraire prenom/nom du representant + $firstName = $raisonSociale; + $lastName = ''; + if (null !== $representant && '' !== $representant) { + $parts = explode(' ', $representant, 2); + $firstName = $parts[0]; + $lastName = $parts[1] ?? ''; + } + + try { + $user = $userManagement->createBaseUser($email, $firstName, $lastName, ['ROLE_CUSTOMER']); + $customer = new Customer($user); + $customer->setRaisonSociale($raisonSociale); + + if (null !== $siret && '' !== $siret) { + $customer->setSiret($siret); + } + if (null !== $adresse && '' !== $adresse) { + $customer->setAddress($adresse); + } + if (null !== $telephone && '' !== $telephone) { + $customer->setPhone($telephone); + } + if (null !== $firstName && '' !== $firstName) { + $customer->setFirstName($firstName); + } + if ('' !== $lastName) { + $customer->setLastName($lastName); + } + + $em->persist($customer); + $em->flush(); + + // Envoyer l'email de bienvenue avec le lien de creation de mot de passe + $setPasswordUrl = $this->generateUrl('app_set_password', [ + 'token' => $user->getTempPassword(), + ], \Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL); + + $mailer->sendEmail( + $email, + 'Bienvenue - Votre espace client E-Cosplay', + $twig->render('emails/client_created.html.twig', [ + 'customer' => $customer, + 'user' => $user, + 'setPasswordUrl' => $setPasswordUrl, + ]), + null, + null, + false, + ); + + return $customer; + } catch (\Throwable) { + return null; + } } /**