diff --git a/migrations/Version20260129091410.php b/migrations/Version20260129091410.php new file mode 100644 index 0000000..0bf0a0e --- /dev/null +++ b/migrations/Version20260129091410.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE contrats_option ADD details TEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE contrats_option DROP details'); + } +} diff --git a/src/Controller/Dashboard/ContratsController.php b/src/Controller/Dashboard/ContratsController.php index 5f4c6d9..be80f7c 100644 --- a/src/Controller/Dashboard/ContratsController.php +++ b/src/Controller/Dashboard/ContratsController.php @@ -7,15 +7,19 @@ use App\Entity\ContratsLine; use App\Entity\ContratsOption; use App\Entity\ContratsPayments; use App\Entity\Devis; +use App\Entity\Product; use App\Event\Signature\ContratEvent; use App\Form\Type\ContratsType; use App\Logger\AppLogger; use App\Repository\AccountRepository; use App\Repository\DevisRepository; use App\Repository\ContratsRepository; +use App\Service\Mailer\Mailer; use App\Service\Pdf\ContratPdfService; +use App\Service\Pdf\PlPdf; use App\Service\Signature\Client; use Doctrine\ORM\EntityManagerInterface; +use Illuminate\Support\Facades\Mail; use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -85,6 +89,7 @@ class ContratsController extends AbstractController AppLogger $appLogger, EventDispatcherInterface $eventDispatcher, KernelInterface $kernel, + Mailer $mailer, ): Response { if (!$contrat) { throw $this->createNotFoundException('Contrat non trouvé.'); @@ -131,37 +136,182 @@ class ContratsController extends AbstractController $solde = $totalHt - $dejaPaye; - if($request->query->has('act') && $request->query->get('act') === 'cautionCapture') { - $amount = $request->query->get('amountToCapture'); - $paiementCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([ + if($request->query->has('type') && $request->query->get('type') === 'accompte') { + $paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([ 'contrat' => $contrat, - 'type' => 'caution', + 'type' => 'accompte', ]); - $result = $stripeClient->capture($paiementCaution->getPaymentId()); - if($result['state']) { - $contrat->setCautionState("recover"); - $entityManager->persist($contrat); - $entityManager->flush(); - $this->addFlash("success","Caution restitué"); - } else { - $this->addFlash("error",$result['message']); + if(!$paiementAccompte) { + $paiementAccompte = new ContratsPayments(); + $paiementAccompte->setContrat($contrat); + $paiementAccompte->setType('accompte'); } - } - if($request->query->has('act') && $request->query->get('act') === 'cautionRelease') { + $paiementAccompte->setValidateAt(new \DateTimeImmutable()); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable()); + $paiementAccompte->setPaymentAt(new \DateTimeImmutable()); + $paiementAccompte->setAmount( $totalHt * 0.25); + $paiementAccompte->setState("complete"); + $paiementAccompte->setPaymentId(""); + $paiementAccompte->setCard([ + 'type' => 'manuel' + ]); + $pdf = new PlPdf($kernel, $paiementAccompte, $contrat); + $pdf->generate(); + $content = $pdf->Output('S'); + $tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf'; + file_put_contents($tmpSigned, $content); - $paiementCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([ + // On utilise UploadedFile pour simuler un upload propre pour VichUploader + $paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true)); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable('now')); + $entityManager->persist($paiementAccompte); + $entityManager->flush(); + $data = $client->autoSignConfirmedPayment($paiementAccompte); + // 1. Gestion du PDF SIGNÉ + $tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf'; + $signedContent = file_get_contents($data); + file_put_contents($tmpSigned, $signedContent); + + // On utilise UploadedFile pour simuler un upload propre pour VichUploader + $paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true)); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable('now')); + $entityManager->persist($paiementAccompte); + $entityManager->flush(); + $customer = $contrat->getCustomer(); + $subjectCustomer = "[Ludikevent] Confirmation de votre acompte - #" . $contrat->getNumReservation(); + $mailer->send( + $customer->getEmail(), + $customer->getSurname() . ' ' . $customer->getName(), + $subjectCustomer, + "mails/customer/accompte_confirmation.twig", + [ + 'contrat' => $contrat, + 'payment' => $paiementAccompte, + 'customer' => $customer, + 'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()]) + ] + ); + $appLogger->record('PAYMENT','Validation accompte manuel pour contrat #' . $contrat->getNumReservation()); + $this->addFlash("success","Validation accompte effectuée"); + return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]); + } + if($request->query->has('type') && $request->query->get('type') === 'caution') { + $paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([ 'contrat' => $contrat, 'type' => 'caution', ]); - $result = $stripeClient->cancelPayment($paiementCaution->getPaymentId()); - if($result['state']) { - $contrat->setCautionState("restitue"); - $entityManager->persist($contrat); - $entityManager->flush(); - $this->addFlash("success","Caution restitué"); - } else { - $this->addFlash("error",$result['message']); + if(!$paiementAccompte) { + $paiementAccompte = new ContratsPayments(); + $paiementAccompte->setContrat($contrat); + $paiementAccompte->setType('caution'); } + $paiementAccompte->setValidateAt(new \DateTimeImmutable()); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable()); + $paiementAccompte->setPaymentAt(new \DateTimeImmutable()); + $paiementAccompte->setAmount( $totalCaution); + $paiementAccompte->setState("complete"); + $paiementAccompte->setPaymentId(""); + $paiementAccompte->setCard([ + 'type' => 'manuel' + ]); + $pdf = new PlPdf($kernel, $paiementAccompte, $contrat); + $pdf->generate(); + $content = $pdf->Output('S'); + $tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf'; + file_put_contents($tmpSigned, $content); + + // On utilise UploadedFile pour simuler un upload propre pour VichUploader + $paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true)); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable('now')); + $entityManager->persist($paiementAccompte); + $entityManager->flush(); + $data = $client->autoSignConfirmedPayment($paiementAccompte); + // 1. Gestion du PDF SIGNÉ + $tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf'; + $signedContent = file_get_contents($data); + file_put_contents($tmpSigned, $signedContent); + + // On utilise UploadedFile pour simuler un upload propre pour VichUploader + $paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true)); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable('now')); + $entityManager->persist($paiementAccompte); + $entityManager->flush(); + $customer = $contrat->getCustomer(); + $subjectCustomer = "[Ludikevent] Confirmation de votre caution - #" . $contrat->getNumReservation(); + $mailer->send( + $customer->getEmail(), + $customer->getSurname() . ' ' . $customer->getName(), + $subjectCustomer, + "mails/customer/accompte_confirmation.twig", + [ + 'contrat' => $contrat, + 'payment' => $paiementAccompte, + 'customer' => $customer, + 'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()]) + ] + ); + $appLogger->record('PAYMENT','Validation caution manuel pour contrat #' . $contrat->getNumReservation()); + $this->addFlash("success","Validation caution effectuée"); + return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]); + } + if($request->query->has('type') && $request->query->get('type') === 'solde') { + $paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([ + 'contrat' => $contrat, + 'type' => 'solde', + ]); + if(!$paiementAccompte) { + $paiementAccompte = new ContratsPayments(); + $paiementAccompte->setContrat($contrat); + $paiementAccompte->setType('solde'); + } + $paiementAccompte->setValidateAt(new \DateTimeImmutable()); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable()); + $paiementAccompte->setPaymentAt(new \DateTimeImmutable()); + $paiementAccompte->setAmount( $totalHt); + $paiementAccompte->setState("complete"); + $paiementAccompte->setPaymentId(""); + $paiementAccompte->setCard([ + 'type' => 'manuel' + ]); + $pdf = new PlPdf($kernel, $paiementAccompte, $contrat); + $pdf->generate(); + $content = $pdf->Output('S'); + $tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf'; + file_put_contents($tmpSigned, $content); + + // On utilise UploadedFile pour simuler un upload propre pour VichUploader + $paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true)); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable('now')); + $entityManager->persist($paiementAccompte); + $entityManager->flush(); + $data = $client->autoSignConfirmedPayment($paiementAccompte); + // 1. Gestion du PDF SIGNÉ + $tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf'; + $signedContent = file_get_contents($data); + file_put_contents($tmpSigned, $signedContent); + + // On utilise UploadedFile pour simuler un upload propre pour VichUploader + $paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true)); + $paiementAccompte->setUpdateAt(new \DateTimeImmutable('now')); + $entityManager->persist($paiementAccompte); + $entityManager->flush(); + $customer = $contrat->getCustomer(); + $subjectCustomer = "[Ludikevent] Votre réservation est désormais soldée - #" . $contrat->getNumReservation(); + $mailer->send( + $customer->getEmail(), + $customer->getSurname() . ' ' . $customer->getName(), + $subjectCustomer, + "mails/customer/accompte_confirmation.twig", + [ + 'contrat' => $contrat, + 'payment' => $paiementAccompte, + 'customer' => $customer, + 'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()]) + ] + ); + $appLogger->record('PAYMENT','Validation solde manuel pour contrat #' . $contrat->getNumReservation()); + $this->addFlash("success","Validation solde effectuée"); + return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]); } return $this->render('dashboard/contrats/view.twig', [ @@ -192,7 +342,7 @@ class ContratsController extends AbstractController $c = new Contrats(); $lines = [['id' => 0, 'name' => '', 'priceHt1Day' => 0, 'priceHtSupDay' => 0, 'caution' => 0]]; - $options = [['id' => 0, 'name' => '', 'priceHt' => 0]]; + $options = [['id' => 0, 'name' => '', 'priceHt' => 0,'details'=>'']]; if ($devis instanceof Devis) { $c->setDateAt($devis->getStartAt()); @@ -200,6 +350,7 @@ class ContratsController extends AbstractController $c->setCustomer($devis->getCustomer()); $c->setDevis($devis); + // Mapping adresse de l'événement if ($devis->getAddressShip()) { $c->setAddressEvent($devis->getAddressShip()->getAddress()); @@ -213,19 +364,22 @@ class ContratsController extends AbstractController $options = []; foreach ($devis->getDevisLines() as $line) { + $p = $entityManager->getRepository(Product::class)->findOneBy(['name'=>$line->getProduct()]); + $lines[] = [ 'id' => $line->getId(), - 'name' => $line->getProduct()->getName() . " - " . $line->getProduct()->getRef(), + 'name' =>$p->getName() . " - " . $p->getRef(), 'priceHt1Day' => $line->getPriceHt(), 'priceHtSupDay' => $line->getPriceHtSup(), - 'caution' => $line->getProduct()->getCaution(), + 'caution' => $p->getCaution(), ]; } foreach ($devis->getDevisOptions() as $line) { $options[] = [ 'id' => $line->getId(), - 'name' => $line->getOption()->getName(), + 'name' => $line->getOption(), + 'details' => $line->getDetails(), 'priceHt' => $line->getPriceHt(), ]; } @@ -257,6 +411,7 @@ class ContratsController extends AbstractController $vc = new ContratsOption(); $vc->setContrat($c); $vc->setName($line['name']); + $vc->setDetails($line['details']); $vc->setPrice($line['priceHt']); $entityManager->persist($vc); } diff --git a/src/Controller/SignatureController.php b/src/Controller/SignatureController.php index c9f0603..6a840b5 100644 --- a/src/Controller/SignatureController.php +++ b/src/Controller/SignatureController.php @@ -158,18 +158,6 @@ class SignatureController extends AbstractController // 4. Sauvegarde en base de données $devis->setUpdateAt(new \DateTimeImmutable()); - - foreach ($devis->getDevisLines() as $line) { - $product = $line->getProduct(); - - $productReserve = new ProductReserve(); - $productReserve->setProduct($product); - $productReserve->setCustomer($devis->getCustomer()); - $productReserve->setStartAt($devis->getStartAt()); - $productReserve->setEndAt($devis->getEndAt()); - $productReserve->setDevis($devis); - $entityManager->persist($productReserve); - } $entityManager->persist($devis); $entityManager->flush(); diff --git a/src/Entity/ContratsOption.php b/src/Entity/ContratsOption.php index 9cb9716..df5d8d1 100644 --- a/src/Entity/ContratsOption.php +++ b/src/Entity/ContratsOption.php @@ -3,6 +3,7 @@ namespace App\Entity; use App\Repository\ContratsOptionRepository; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: ContratsOptionRepository::class)] @@ -22,6 +23,9 @@ class ContratsOption #[ORM\Column] private ?float $price = null; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $details = null; + public function getId(): ?int { return $this->id; @@ -62,4 +66,16 @@ class ContratsOption return $this; } + + public function getDetails(): ?string + { + return $this->details; + } + + public function setDetails(?string $details): static + { + $this->details = $details; + + return $this; + } } diff --git a/src/Service/Pdf/PlPdf.php b/src/Service/Pdf/PlPdf.php index 89e53f6..55a4e78 100644 --- a/src/Service/Pdf/PlPdf.php +++ b/src/Service/Pdf/PlPdf.php @@ -134,7 +134,12 @@ class PlPdf extends Fpdf $this->SetFont('Arial', 'B', 10); $this->Cell(50, 8, $this->clean("Mode de règlement :"), 0, 0); $this->SetFont('Arial', '', 10); - $this->Cell(0, 8, $this->clean($this->contratsPayments->getCard()['type'] ?? 'Carte Bancaire'), 0, 1); + if($this->contratsPayments->getCard()['type'] == "manuel") { + $this->Cell(0, 8, $this->clean("Paiement valider par Ludikevent"), 0, 1); + } else { + $this->Cell(0, 8, $this->clean($this->contratsPayments->getCard()['type'] ?? 'Carte Bancaire'), 0, 1); + } + $this->SetFont('Arial', 'B', 10); $this->Cell(50, 8, $this->clean("Référence transaction :"), 0, 0); diff --git a/templates/dashboard/contrats/add.twig b/templates/dashboard/contrats/add.twig index 7636a30..5096ad1 100644 --- a/templates/dashboard/contrats/add.twig +++ b/templates/dashboard/contrats/add.twig @@ -224,7 +224,7 @@
{{ contrat.customer.surname }} {{ contrat.customer.name }}
-{{ contrat.customer.email }}
+ {# 2. CLIENT #} +{{ contrat.customer.surname }} {{ contrat.customer.name }}
+{{ contrat.customer.email }}
+- {{ contrat.townEvent }} -
-{{ contrat.zipCodeEvent }}
-{{ contrat.townEvent }}
+{{ contrat.zipCodeEvent }}
Informations Locataire
+Contrat
- {% if contrat.isSigned %} -Signé
- {% else %} - À SIGNER ICI - {% endif %} +{{ contrat.dateAt|date('d/m/Y') }} - {{ contrat.endAt|date('d/m/Y') }}
+Acompte (Arrhes)
- {% if acompteOk %} -Encaissé
- {% else %} - Régler {{ arrhes|number_format(2, ',', ' ') }}€ - {% endif %} +{{ contrat.addressEvent }}
+{{ contrat.zipCodeEvent }} {{ contrat.townEvent }}
Garantie (Caution)
- - {% if not cautionOk %} - {# ÉTAT 1 : ATTENTE DE DÉPÔT #} -Non Déposée
-Attendu : {{ totalCaution|number_format(2, ',', ' ') }}€
-Empreinte active
-Encaissé
+ {% endif %} +État du solde
- {% if soldeOk %} -Dossier Soldé
+ Solde Final + {% if not soldeOk %} + + Régler le solde + {% else %} - +Totalité payée
{% endif %}{{ contrat.customer.surname }}
-{{ contrat.customer.name }}
-Caution : {{ product.caution }}€
+Adresse
-
- {{ contrat.addressEvent }}
- {{ contrat.zipCodeEvent }} {{ contrat.townEvent|upper }}
-
Du
-{{ contrat.dateAt|date('d/m/Y') }}
+ {# OPTIONS #} +{{ product.details }}
+Au
-{{ contrat.endAt|date('d/m/Y') }}
-Acompte Requis
-{{ arrhes|number_format(2, ',', ' ') }}€
-Montant Caution
-{{ totalCaution|number_format(2, ',', ' ') }}€
-| Date | -Type | -Montant | -Action | +|
|---|---|---|---|---|
| Transaction | +Type | +Montant | +Justificatif | |
| {{ payment.paymentAt|date('d/m/Y H:i') }} | +{{ payment.paymentAt|date('d/m/Y H:i') }} | - - {{ payment.type|replace({'_': ' '}) }} - + + {{ payment.type|replace({'_': ' '}) }} + | {{ payment.amount|number_format(2, ',', ' ') }}€ |
-
- |
| Aucune transaction enregistrée | -||||
| Aucun paiement effectué | ||||