```
✨ feat(DevisController): Améliore la gestion et l'édition des devis
Corrige des bugs et améliore la création/édition des devis, incluant options et lignes, et la gestion des signatures.
```
This commit is contained in:
6
.env
6
.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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
32
migrations/Version20260129083756.php
Normal file
32
migrations/Version20260129083756.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260129083756 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->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');
|
||||
}
|
||||
}
|
||||
38
migrations/Version20260129084026.php
Normal file
38
migrations/Version20260129084026.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260129084026 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->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)');
|
||||
}
|
||||
}
|
||||
BIN
sign_ludikevent.jpeg
Normal file
BIN
sign_ludikevent.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,15 +41,9 @@ class Options
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, DevisOptions>
|
||||
*/
|
||||
#[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<int, DevisOptions>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -172,10 +172,10 @@
|
||||
<input type="hidden" name="options[{{ key }}][id]" value="{{ option.id }}">
|
||||
{% endif %}
|
||||
{# 1. PRODUIT #}
|
||||
<div class="lg:col-span-9">
|
||||
<div class="lg:col-span-7">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Produit / Prestation</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type="text" name="lines[{{ key }}][product]" value="{{ option.product }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 pl-5 pr-12 text-sm">
|
||||
<input type="text" name="options[{{ key }}][product]" value="{{ option.product }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 pl-5 pr-12 text-sm">
|
||||
|
||||
{# BOUTON RECHERCHER #}
|
||||
<button is="search-optionsdevis" type="button" class="absolute right-2 p-2 bg-purple-500/10 hover:bg-purple-500 text-purple-400 hover:text-white rounded-xl transition-all duration-300 group/search" title="Rechercher un produit">
|
||||
@@ -183,6 +183,12 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lg:col-span-2">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Détails</label>
|
||||
<div class="relative flex items-center">
|
||||
<input type="text" name="options[{{ key }}][details]" value="{{ option.details }}" class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 pl-5 pr-12 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
{# 3. PRIX HT J1 #}
|
||||
<div class="lg:col-span-2">
|
||||
<label class="{{ label_class }}">Prix Ht (€)</label>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<div class="flex items-center justify-end space-x-2">
|
||||
|
||||
{# Renvoyer lien de signature #}
|
||||
{% if quote.state == "created_waitsign" %}
|
||||
{% if quote.state == "created_waitsign" and quote.state == "wait-send" %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_devis', {resend: quote.id}) }}"
|
||||
title="Renvoyer le lien de signature"
|
||||
class="p-2 bg-indigo-600/10 hover:bg-indigo-600 text-indigo-500 hover:text-white rounded-xl transition-all border border-indigo-500/20 shadow-lg shadow-indigo-600/5">
|
||||
|
||||
Reference in New Issue
Block a user