```
✨ feat(contrat/view): Ajoute l'attribut data-turbo="false" au lien d'acompte. 🐛 fix(RedirecListener): Exclut les webhooks de la redirection ngrok. ✨ feat(Webhooks): Crée des ProductReserve lors du paiement d'un acompte. ✨ feat(ContratPdfService): Remplace le code-barres par un QR code. ✨ feat(ContratController): Gère le paiement de l'acompte via Stripe. ```
This commit is contained in:
@@ -15,6 +15,7 @@ use App\Repository\ContratsRepository;
|
||||
use App\Repository\CustomerAddressRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Pdf\ContratPdfService;
|
||||
use App\Service\Pdf\PlPdf;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
@@ -227,7 +228,6 @@ class ContratController extends AbstractController
|
||||
|
||||
|
||||
if ($request->query->has('act') && $request->query->get('act') === 'accomptePay') {
|
||||
// On vérifie s'il n'y a pas déjà un acompte payé ou en cours
|
||||
$existingPayment = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
|
||||
'contrat' => $contrat,
|
||||
'type' => 'accompte',
|
||||
@@ -400,7 +400,6 @@ class ContratController extends AbstractController
|
||||
return $this->file($path, 'confirmation-paiement.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('reservation/contrat/view.twig', [
|
||||
'contrat' => $contrat,
|
||||
'days' => $totalDays,
|
||||
|
||||
@@ -45,28 +45,42 @@ class Webhooks extends AbstractController
|
||||
$pl->setValidateAt(new \DateTimeImmutable('now'));
|
||||
$pl->setCard($client->paymentMethod($session['payment_intent']));
|
||||
|
||||
$contrat = $pl->getContrat();
|
||||
$customer = $contrat->getCustomer();
|
||||
|
||||
// Gestion des réservations produits (si pas déjà géré par un devis)
|
||||
if (!$contrat->getDevis() instanceof Devis) {
|
||||
|
||||
$contrat = $pl->getContrat();
|
||||
|
||||
if($pl->getType() == "accompte") {
|
||||
foreach ($contrat->getContratsLines() as $line) {
|
||||
$product = $productRepository->findOneBy(['name' => $line->getName()]);
|
||||
if ($product instanceof Product) {
|
||||
$pres = new ProductReserve();
|
||||
$pres->setProduct($product); // Corrigé ici
|
||||
$pres->setStartAt($contrat->getDateAt());
|
||||
$pres->setEndAt($contrat->getEndAt());
|
||||
$pres->setContrat($contrat);
|
||||
$entityManager->persist($pres);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($contrat->getDevis()->getProductReserve() as $productReserve) {
|
||||
$productReserve->setContrat($contrat);
|
||||
$entityManager->persist($productReserve);
|
||||
$p = $productRepository->findOneBy(['name' => $line->getProduct()]);
|
||||
$pr = new ProductReserve();
|
||||
$pr->setContrat($contrat);
|
||||
$pr->setProduct($p);
|
||||
$pr->setStartAt($contrat->getDateAt());
|
||||
$pr->setEndAt($contrat->getEndAt());
|
||||
$pr->setCustomer($contrat->getCustomer());
|
||||
$pr->setDevis($contrat->getDevis());
|
||||
$entityManager->persist($pr);
|
||||
$entityManager->flush();
|
||||
}
|
||||
}
|
||||
$contrat = $pl->getContrat();
|
||||
|
||||
if($pl->getType() == "accompte") {
|
||||
foreach ($contrat->getContratsLines() as $line) {
|
||||
$r = explode(" - ",$line->getName());
|
||||
if(isset($r[1])) {
|
||||
$p = $productRepository->findOneBy(['ref' => $r[1]]);
|
||||
$pr = new ProductReserve();
|
||||
$pr->setContrat($contrat);
|
||||
$pr->setProduct($p);
|
||||
$pr->setStartAt($contrat->getDateAt());
|
||||
$pr->setEndAt($contrat->getEndAt());
|
||||
$pr->setCustomer($contrat->getCustomer());
|
||||
$entityManager->persist($pr);
|
||||
}
|
||||
}
|
||||
}
|
||||
$customer = $contrat->getCustomer();
|
||||
|
||||
$pdf = new PlPdf($kernel, $pl, $contrat);
|
||||
$pdf->generate();
|
||||
|
||||
@@ -26,7 +26,7 @@ class RedirecListener
|
||||
|
||||
// --- Logique Spécifique DEV / NGROK ---
|
||||
// Si on est en dev et que le host contient "ngrok-free.app" (ou "ngrok")
|
||||
if ($this->isDev && str_contains($host, 'ngrok')) {
|
||||
if ($this->isDev && str_contains($host, 'ngrok') && !str_contains($pathInfo,'webhooks')) {
|
||||
// On redirige vers le domaine local esyweb.local en conservant le chemin et les paramètres
|
||||
$newUrl = "https://esyweb.local" . $pathInfo;
|
||||
$queryString = $request->getQueryString();
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
namespace App\Service\Pdf;
|
||||
|
||||
use App\Entity\Contrats;
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
use Endroid\QrCode\Encoding\Encoding;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\Label\LabelAlignment;
|
||||
use Endroid\QrCode\RoundBlockSizeMode;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
use Fpdf\Fpdf;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
@@ -28,38 +34,30 @@ class ContratPdfService extends Fpdf
|
||||
/**
|
||||
* Génère un code-barres Code 39
|
||||
*/
|
||||
private function Code39($xpos, $ypos, $code, $baseline = 0.5, $height = 5)
|
||||
private function insertQRCode(float $x, float $y, string $data, int $size = 22): void
|
||||
{
|
||||
$wide = $baseline * 2.25;
|
||||
$narrow = $baseline;
|
||||
$gap = $narrow;
|
||||
$builder = new Builder(
|
||||
writer: new PngWriter(),
|
||||
writerOptions: [],
|
||||
validateResult: false,
|
||||
data: $data,
|
||||
encoding: new Encoding('UTF-8'),
|
||||
errorCorrectionLevel: ErrorCorrectionLevel::High,
|
||||
size: 300,
|
||||
margin: 0,
|
||||
roundBlockSizeMode: RoundBlockSizeMode::Margin,
|
||||
);
|
||||
|
||||
$barChar = [
|
||||
'0' => 'nnnwwnwnn', '1' => 'wnnwnnnnw', '2' => 'nnwwnnnnw', '3' => 'wnwwnnnnn',
|
||||
'4' => 'nnnwwnnnw', '5' => 'wnnwwnnnn', '6' => 'nnwwnwnnn', '7' => 'nnnwnnwnw',
|
||||
'8' => 'wnnwnnwnn', '9' => 'nnwwnnwnn', 'A' => 'wnnnnwnnw', 'B' => 'nnwnnwnnw',
|
||||
'C' => 'wnwnnwnnn', 'D' => 'nnnnwwnnw', 'E' => 'wnnnwwnnn', 'F' => 'nnwnwwnnn',
|
||||
'G' => 'nnnnnwwnw', 'H' => 'wnnnnwwnn', 'I' => 'nnwnnwwnn', 'J' => 'nnnnwwwnn',
|
||||
'K' => 'wnnnnnnww', 'L' => 'nnwnnnnww', 'M' => 'wnwnnnnwn', 'N' => 'nnnnwnnww',
|
||||
'O' => 'wnnnwnnwn', 'P' => 'nnwnwnnwn', 'Q' => 'nnnnnnwww', 'R' => 'wnnnnnwwn',
|
||||
'S' => 'nnwnnnwwn', 'T' => 'nnnnwnwwn', 'U' => 'wwnnnnnnw', 'V' => 'nwwnnnnnw',
|
||||
'W' => 'wwwnnnnnn', 'X' => 'nwnnwnnnw', 'Y' => 'wwnnwnnnn', 'Z' => 'nwwnwnnnn',
|
||||
'-' => 'nwnnnnwnw', '.' => 'wwnnnnwnn', ' ' => 'nwwnnnwnn', '*' => 'nwnnwnwnn',
|
||||
'$' => 'nwnwnwnnn', '/' => 'nwnwnnnwn', '+' => 'nwnnnwnwn', '%' => 'nnnwnwnwn'
|
||||
];
|
||||
$result = $builder->build();
|
||||
|
||||
$this->SetFillColor(0);
|
||||
$code = '*' . strtoupper($code) . '*';
|
||||
for ($i = 0; $i < strlen($code); $i++) {
|
||||
$char = $code[$i];
|
||||
if (!isset($barChar[$char])) continue;
|
||||
$seq = $barChar[$char];
|
||||
for ($bar = 0; $bar < 9; $bar++) {
|
||||
if ($seq[$bar] == 'n') $lineWidth = $narrow; else $lineWidth = $wide;
|
||||
if ($bar % 2 == 0) $this->Rect($xpos, $ypos, $lineWidth, $height, 'F');
|
||||
$xpos += $lineWidth;
|
||||
}
|
||||
$xpos += $gap;
|
||||
// Utilisation d'un fichier temporaire pour FPDF
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'qr_');
|
||||
$result->saveToFile($tmpFile);
|
||||
|
||||
$this->Image($tmpFile, $x, $y, $size, $size, 'PNG');
|
||||
|
||||
if (file_exists($tmpFile)) {
|
||||
unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +111,7 @@ class ContratPdfService extends Fpdf
|
||||
|
||||
|
||||
// Paramètres : X=160 (à droite), Y=42, Code=Numéro, Largeur barre=0.3, Hauteur=8
|
||||
$this->Code39(155, 41, $this->contrats->getNumReservation(), 0.3, 8);
|
||||
$this->insertQRCode(175, 10, $this->contrats->getNumReservation(), 22);
|
||||
|
||||
$this->Ln(10); // Retour à la ligne après le titre et le barcode
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-3xl font-black text-slate-900 italic mb-4">{{ arrhes|number_format(2, ',', ' ') }}€</p>
|
||||
{% if contrat.signed %}
|
||||
<a href="{{ path('gestion_contrat_view', {'num': contrat.numReservation,'act':'accomptePay'}) }}" class="block w-full bg-slate-900 text-white py-4 rounded-xl font-black uppercase text-xs hover:bg-blue-600 transition-all shadow-md">Régler l'acompte</a>
|
||||
<a data-turbo="false" href="{{ path('gestion_contrat_view', {'num': contrat.numReservation,'act':'accomptePay'}) }}" class="block w-full bg-slate-900 text-white py-4 rounded-xl font-black uppercase text-xs hover:bg-blue-600 transition-all shadow-md">Régler l'acompte</a>
|
||||
{% else %}
|
||||
<div class="p-3 bg-amber-50 rounded-xl border border-amber-100">
|
||||
<span class="text-[9px] text-amber-600 font-black uppercase tracking-tighter">Attente de signature du contrat</span>
|
||||
|
||||
Reference in New Issue
Block a user