diff --git a/.env b/.env index ea73d91..31a7bd6 100644 --- a/.env +++ b/.env @@ -83,9 +83,9 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR STRIPE_WEBHOOKS_SECRET= -SIGN_URL=https://a292239e6756.ngrok-free.app -STRIPE_BASEURL=https://a292239e6756.ngrok-free.app -CONTRAT_BASEURL=https://a292239e6756.ngrok-free.app +SIGN_URL=https://790f740ccd60.ngrok-free.app +STRIPE_BASEURL=https://790f740ccd60.ngrok-free.app +CONTRAT_BASEURL=https://790f740ccd60.ngrok-free.app MINIO_S3_URL= MINIO_S3_CLIENT_ID= diff --git a/assets/admin.scss b/assets/admin.scss index 579716b..141db68 100644 --- a/assets/admin.scss +++ b/assets/admin.scss @@ -47,6 +47,12 @@ details { } } +.ts-control { + .item { + color: white !important; + } +} + /* --- TOMSELECT CUSTOM DARK THEME --- */ /* On augmente la spécificité par le nesting pour éviter les !important */ .ts-wrapper { diff --git a/migrations/Version20260129083756.php b/migrations/Version20260129083756.php new file mode 100644 index 0000000..3cc27d6 --- /dev/null +++ b/migrations/Version20260129083756.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE devis_options 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 devis_options DROP details'); + } +} diff --git a/migrations/Version20260129084026.php b/migrations/Version20260129084026.php new file mode 100644 index 0000000..e4b54f4 --- /dev/null +++ b/migrations/Version20260129084026.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE devis_options DROP CONSTRAINT fk_42db61dba7c41d6f'); + $this->addSql('DROP INDEX idx_42db61dba7c41d6f'); + $this->addSql('ALTER TABLE devis_options ADD option TEXT NOT NULL'); + $this->addSql('ALTER TABLE devis_options DROP option_id'); + } + + 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 devis_options ADD option_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE devis_options DROP option'); + $this->addSql('ALTER TABLE devis_options ADD CONSTRAINT fk_42db61dba7c41d6f FOREIGN KEY (option_id) REFERENCES options (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX idx_42db61dba7c41d6f ON devis_options (option_id)'); + } +} diff --git a/sign_ludikevent.jpeg b/sign_ludikevent.jpeg new file mode 100644 index 0000000..8f9c062 Binary files /dev/null and b/sign_ludikevent.jpeg differ diff --git a/src/Controller/Dashboard/DevisController.php b/src/Controller/Dashboard/DevisController.php index eebc812..6a00347 100644 --- a/src/Controller/Dashboard/DevisController.php +++ b/src/Controller/Dashboard/DevisController.php @@ -6,6 +6,7 @@ use App\Entity\CustomerAddress; use App\Entity\Devis; use App\Entity\DevisLine; use App\Entity\DevisOptions; +use App\Entity\Product; use App\Event\Signature\DevisSend; use App\Form\NewDevisType; use App\Logger\AppLogger; @@ -46,16 +47,17 @@ class DevisController extends AbstractController PaginatorInterface $paginator, Request $request, KernelInterface $kernel, - ): Response { - // Gestion du renvoi de la signature if ($request->query->has('resend')) { $quoteId = $request->query->get('resend'); $quote = $devisRepository->find($quoteId); if ($quote instanceof Devis) { + $quote->setState("created_waitsign"); + $entityManager->persist($quote); + $entityManager->flush(); // Déclenchement de l'événement de renvoi $event = new DevisSend($quote); $eventDispatcher->dispatch($event); @@ -145,7 +147,7 @@ class DevisController extends AbstractController $rLine = new DevisLine(); $rLine->setDevi($devis); $rLine->setPos($cd); - $rLine->setProduct($productRepository->find($line['product_id'])); + $rLine->setProduct($line['product']); $rLine->setDay($day); $rLine->setPriceHt(floatval($line['price_ht'])); $rLine->setPriceHtSup(floatval($line['price_sup_ht'])); @@ -154,14 +156,14 @@ class DevisController extends AbstractController foreach ($_POST['options'] as $line) { $rLineOptions = new DevisOptions(); $rLineOptions->setDevis($devis); - $rLineOptions->setOption($optionsRepository->find($line['product_id'])); + $rLineOptions->setOption($line['product']); $rLineOptions->setPriceHt(floatval($line['price_ht'])); $entityManager->persist($rLineOptions); } $entityManager->persist($devis); $entityManager->flush(); - $docusealService = new DevisPdfService($kernel, $devis, true); + $docusealService = new DevisPdfService($kernel, $devis, $entityManager->getRepository(Product::class),true); $contentDocuseal = $docusealService->generate(); @@ -172,7 +174,7 @@ class DevisController extends AbstractController $devis->setDevisDocuSealFile($fileDocuseal); - $devisService = new DevisPdfService($kernel, $devis, false); + $devisService = new DevisPdfService($kernel, $devis, $entityManager->getRepository(Product::class), false); $contentDevis = $devisService->generate(); $tmpPathDevis = sys_get_temp_dir() . '/devis_' . uniqid() . '.pdf'; @@ -182,13 +184,10 @@ class DevisController extends AbstractController $devis->setDevisFile($fileDevis); - $devis->setState("created_waitsign"); + $devis->setState("wait-send"); $devis->setUpdateAt(new \DateTimeImmutable()); $entityManager->flush(); $client->createSubmissionDevis($devis); - - $event = new DevisSend($devis); - $eventDispatcher->dispatch($event); $this->addFlash('success', sprintf('Le devis %s a été crée.', $devis->getNum())); return $this->redirectToRoute('app_crm_devis'); @@ -206,6 +205,7 @@ class DevisController extends AbstractController 'options' => [ [ 'product' => '', + 'details' => '', 'price_ht' => '', ] ] @@ -223,27 +223,39 @@ class DevisController extends AbstractController $devis->setBillAddress($customerAddress->find($_POST['devis']['bill_address'])); $devis->setAddressShip($customerAddress->find($_POST['devis']['ship_address'])); $devis->setCustomer($customerRepository->find($_POST['new_devis']['customer'])); + $interval = $devis->getStartAt()->diff($devis->getEndAt()); + $day = $interval->days; foreach ($_POST['lines'] as $cd => $line) { - $rLine = $devisLineRepository->find($line['id']); - $rLine->setDevi($devis); - $rLine->setPos($cd); - $rLine->setProduct($productRepository->find($line['product_id'])); - $rLine->setDay($line['days']); + if($line['id'] != "") { + $rLine = $devisLineRepository->find($line['id']); + } else { + $rLine = new DevisLine(); + $rLine->setDevi($devis); + $rLine->setPos($cd); + } + $rLine->setDay($day); + $rLine->setProduct($line['product']); $rLine->setPriceHt(floatval($line['price_ht'])); $rLine->setPriceHtSup(floatval($line['price_sup_ht'])); $entityManager->persist($rLine); } foreach ($_POST['options'] as $line) { - $rLineOptions = $devisOptionsRepository->find($line['id']); - $rLineOptions->setOption($optionsRepository->find($line['product_id'])); + if($line['id'] != "") { + $rLineOptions = $devisOptionsRepository->find($line['id']); + } else { + $rLineOptions = new DevisOptions(); + $rLineOptions->setDevis($devis); + } + $rLineOptions->setOption($line['product']); + $rLineOptions->setDetails($line['details']); $rLineOptions->setPriceHt(floatval($line['price_ht'])); $entityManager->persist($rLineOptions); } $entityManager->persist($devis); $entityManager->flush(); - $docusealService = new DevisPdfService($kernel, $devis, true); + $docusealService = new DevisPdfService($kernel, $devis, $entityManager->getRepository(Product::class),true); $contentDocuseal = $docusealService->generate(); @@ -254,7 +266,7 @@ class DevisController extends AbstractController $devis->setDevisDocuSealFile($fileDocuseal); - $devisService = new DevisPdfService($kernel, $devis, false); + $devisService = new DevisPdfService($kernel, $devis, $entityManager->getRepository(Product::class),false); $contentDevis = $devisService->generate(); $tmpPathDevis = sys_get_temp_dir() . '/devis_' . uniqid() . '.pdf'; @@ -264,11 +276,10 @@ class DevisController extends AbstractController $devis->setDevisFile($fileDevis); $devis->setUpdateAt(new \DateTimeImmutable()); + $devis->setState("wait-send"); $entityManager->flush(); $client->createSubmissionDevis($devis); - $event = new DevisSend($devis); - $eventDispatcher->dispatch($event); $this->addFlash('success', sprintf('Le devis %s a été modifiée.', $devis->getNum())); return $this->redirectToRoute('app_crm_devis'); @@ -277,7 +288,7 @@ class DevisController extends AbstractController $lines =[ [ 'id' => '', - 'product_id' => '', + 'product' => '', 'days'=>'', 'price_ht' => '', 'price_sup_ht' =>'' @@ -286,7 +297,8 @@ class DevisController extends AbstractController $options = [ [ 'id' => '', - 'product_id' => '', + 'product' => '', + 'details' => '', 'price_ht' => '', ] ]; @@ -295,7 +307,7 @@ class DevisController extends AbstractController foreach ($devis->getDevisLines() as $key => $line) { $lines[$key] = [ 'id' => $line->getId(), - 'product_id' => $line->getProduct()->getId(), + 'product' => $line->getProduct(), 'days' => $line->getDay(), 'price_ht' => $line->getPriceHt(), 'price_sup_ht' => $line->getPriceHtSup() @@ -304,7 +316,8 @@ class DevisController extends AbstractController foreach ($devis->getDevisOptions() as $key => $line) { $options[$key] = [ 'id' => $line->getId(), - 'product_id' => $line->getOption()->getId(), + 'details' => $line->getDetails(), + 'product' => $line->getOption(), 'price_ht' => $line->getPriceHt(), ]; } diff --git a/src/Controller/ReserverController.php b/src/Controller/ReserverController.php index e7fd999..b155148 100644 --- a/src/Controller/ReserverController.php +++ b/src/Controller/ReserverController.php @@ -213,6 +213,7 @@ class ReserverController extends AbstractController #[Route('/connexion', name: 'reservation_login')] public function revervationLogin(AuthenticationUtils $authenticationUtils): Response { + return $this->redirectToRoute('reservation'); return $this->render('revervation/login.twig',[ 'last_username' => $authenticationUtils->getLastUsername(), 'error' => $authenticationUtils->getLastAuthenticationError() @@ -231,6 +232,8 @@ class ReserverController extends AbstractController EntityManagerInterface $em, UserPasswordHasherInterface $hasher ): Response { + return $this->redirectToRoute('reservation'); + if ($request->isMethod('POST')) { $payload = $request->getPayload(); diff --git a/src/Entity/DevisOptions.php b/src/Entity/DevisOptions.php index 73255e2..386aa4f 100644 --- a/src/Entity/DevisOptions.php +++ b/src/Entity/DevisOptions.php @@ -3,6 +3,7 @@ namespace App\Entity; use App\Repository\DevisOptionsRepository; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: DevisOptionsRepository::class)] @@ -16,12 +17,16 @@ class DevisOptions #[ORM\ManyToOne(inversedBy: 'devisOptions')] private ?Devis $devis = null; - #[ORM\ManyToOne(inversedBy: 'devisOptions')] - private ?Options $option = null; #[ORM\Column] private ?float $priceHt = null; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $details = null; + + #[ORM\Column(type: Types::TEXT)] + private ?string $option = null; + public function getId(): ?int { return $this->id; @@ -39,17 +44,6 @@ class DevisOptions return $this; } - public function getOption(): ?Options - { - return $this->option; - } - - public function setOption(?Options $option): static - { - $this->option = $option; - - return $this; - } public function getPriceHt(): ?float { @@ -62,4 +56,28 @@ class DevisOptions return $this; } + + public function getDetails(): ?string + { + return $this->details; + } + + public function setDetails(?string $details): static + { + $this->details = $details; + + return $this; + } + + public function getOption(): ?string + { + return $this->option; + } + + public function setOption(string $option): static + { + $this->option = $option; + + return $this; + } } diff --git a/src/Entity/Options.php b/src/Entity/Options.php index aa60a0e..9c1178f 100644 --- a/src/Entity/Options.php +++ b/src/Entity/Options.php @@ -41,15 +41,9 @@ class Options #[ORM\Column(nullable: true)] private ?\DateTimeImmutable $updatedAt = null; - /** - * @var Collection - */ - #[ORM\OneToMany(targetEntity: DevisOptions::class, mappedBy: 'option')] - private Collection $devisOptions; public function __construct() { - $this->devisOptions = new ArrayCollection(); } @@ -153,33 +147,4 @@ class Options return$s->slugify($this->id."-".$this->name); } - /** - * @return Collection - */ - public function getDevisOptions(): Collection - { - return $this->devisOptions; - } - - public function addDevisOption(DevisOptions $devisOption): static - { - if (!$this->devisOptions->contains($devisOption)) { - $this->devisOptions->add($devisOption); - $devisOption->setOption($this); - } - - return $this; - } - - public function removeDevisOption(DevisOptions $devisOption): static - { - if ($this->devisOptions->removeElement($devisOption)) { - // set the owning side to null (unless already changed) - if ($devisOption->getOption() === $this) { - $devisOption->setOption(null); - } - } - - return $this; - } } diff --git a/src/Service/Pdf/DevisPdfService.php b/src/Service/Pdf/DevisPdfService.php index ef1e4ef..0fb3a71 100644 --- a/src/Service/Pdf/DevisPdfService.php +++ b/src/Service/Pdf/DevisPdfService.php @@ -3,6 +3,9 @@ namespace App\Service\Pdf; use App\Entity\Devis; +use App\Entity\Product; +use App\Repository\ProductRepository; +use Doctrine\ORM\EntityManagerInterface; use Fpdf\Fpdf; use Symfony\Component\HttpKernel\KernelInterface; @@ -12,8 +15,9 @@ class DevisPdfService extends Fpdf private string $logo; private bool $isExtraPage = false; private bool $isIntegrateDocusealFields; + private ProductRepository $productRepository; - public function __construct(KernelInterface $kernel, Devis $devis,bool $isIntegrateDocusealFields = false, $orientation = 'P', $unit = 'mm', $size = 'A4') + public function __construct( KernelInterface $kernel, Devis $devis, ProductRepository $productRepository, bool $isIntegrateDocusealFields = false, $orientation = 'P', $unit = 'mm', $size = 'A4') { parent::__construct($orientation, $unit, $size); $this->devis = $devis; @@ -22,6 +26,7 @@ class DevisPdfService extends Fpdf $this->AliasNbPages(); $this->SetAutoPageBreak(true, 35); + $this->productRepository = $productRepository; } /** @@ -139,7 +144,11 @@ class DevisPdfService extends Fpdf $totalCaution = 0; $totalHT = 0; foreach ($this->devis->getDevisLines() as $line) { - $totalCaution = $totalCaution + $line->getProduct()->getCaution(); + $p = $this->productRepository->findOneBy(['name'=>$line->getProduct()]); + if($p instanceof Product) { + $totalCaution = $totalCaution + $p->getCaution(); + } + $price1Day = $line->getPriceHt(); $priceSupHT = $line->getPriceHtSup() ?? 0; $nbDays = $line->getDay(); @@ -148,8 +157,11 @@ class DevisPdfService extends Fpdf $lineTotalHT = $price1Day + (max(0, $nbDays - 1) * $priceSupHT); $totalHT += $lineTotalHT; - $productName = $line->getProduct()->getName(); - $ref = $line->getProduct()->getRef(); + $productName = $line->getProduct(); + $ref= ""; + if($p instanceof Product) { + $ref = $totalCaution + $p->getRef(); + } $currentY = $this->GetY(); @@ -191,14 +203,14 @@ class DevisPdfService extends Fpdf foreach ($this->devis->getDevisOptions() as $devisOption) { $option = $devisOption->getOption(); - $priceHT = $option->getPriceHt(); + $priceHT = $devisOption->getPriceHt(); $totalHT += $priceHT; // On l'ajoute au total général $currentY = $this->GetY(); // Colonne Désignation $this->SetXY(10, $currentY); - $this->Cell(150, 8, $this->clean($option->getName()), 'LRB', 0, 'L'); + $this->Cell(150, 8, $this->clean($option." - ".$devisOption->getDetails()), 'LRB', 0, 'L'); // Colonne Prix $this->Cell(40, 8, number_format($priceHT, 2, ',', ' ') . $this->euro(), 'RB', 1, 'R'); diff --git a/src/Service/Signature/Client.php b/src/Service/Signature/Client.php index 6b33949..53db773 100644 --- a/src/Service/Signature/Client.php +++ b/src/Service/Signature/Client.php @@ -32,7 +32,7 @@ class Client // L'URL API est le point d'entrée pour le SDK Docuseal $apiUrl = rtrim("https://signature.esy-web.dev", '/') . '/api'; $this->docuseal = new \Docuseal\Api($key, $apiUrl); - $this->logo = $kernel->getProjectDir()."/public/provider/images/favicon.png"; + $this->logo = $kernel->getProjectDir()."/sign_ludikevent.jpeg"; } /** @@ -42,7 +42,6 @@ class Client { // Si aucune signature n'est lancée, on initialise la soumission if ($devis->getSignatureId() === null) { - // URL où le client sera redirigé après signature $completedRedirectUrl = $this->baseUrl . $this->urlGenerator->generate( 'app_sign_complete', diff --git a/src/Twig/StripeExtension.php b/src/Twig/StripeExtension.php index 272af0b..eaa51cd 100644 --- a/src/Twig/StripeExtension.php +++ b/src/Twig/StripeExtension.php @@ -5,6 +5,7 @@ namespace App\Twig; use App\Entity\Contrats; use App\Entity\ContratsPayments; use App\Entity\Devis; +use App\Entity\DevisOptions; use App\Entity\Product; use App\Service\Stripe\Client; use Doctrine\ORM\EntityManagerInterface; @@ -62,12 +63,9 @@ class StripeExtension extends AbstractExtension } // 2. Calcul des options additionnelles + /** @var DevisOptions $devisOption */ foreach ($devis->getDevisOptions() as $devisOption) { - // On récupère l'entité Option liée à la ligne de liaison - $option = $devisOption->getOption(); - if ($option) { - $totalHT += $option->getPriceHt() ?? 0; - } + $totalHT += $devisOption->getPriceHt() ?? 0; } return (float) $totalHT; diff --git a/templates/dashboard/devis/add.twig b/templates/dashboard/devis/add.twig index 94d7ba8..9ec77b4 100644 --- a/templates/dashboard/devis/add.twig +++ b/templates/dashboard/devis/add.twig @@ -172,10 +172,10 @@ {% endif %} {# 1. PRODUIT #} -
+
- + {# BOUTON RECHERCHER #}
+
+ +
+ +
+
{# 3. PRIX HT J1 #}
diff --git a/templates/dashboard/devis/list.twig b/templates/dashboard/devis/list.twig index 8de443b..0079e79 100644 --- a/templates/dashboard/devis/list.twig +++ b/templates/dashboard/devis/list.twig @@ -102,7 +102,7 @@
{# Renvoyer lien de signature #} - {% if quote.state == "created_waitsign" %} + {% if quote.state == "created_waitsign" and quote.state == "wait-send" %}