feat(templates-points-controle): Ajoute la gestion et l'application de modèles de points de contrôle aux produits.

This commit is contained in:
Serreau Jovann
2026-02-09 07:57:43 +01:00
parent 38f8762efe
commit 81c4fb0df9
6 changed files with 223 additions and 42 deletions

View File

@@ -1,4 +1,3 @@
export class FlowReserve extends HTMLAnchorElement {
constructor() {
super();
@@ -165,55 +164,56 @@ export class FlowReserve extends HTMLAnchorElement {
}
}
ensureSidebarExists() {
if (document.getElementById(this.sidebarId)) return;
ensureSidebarExists() {
if (document.getElementById(this.sidebarId)) return;
const template = `
<div id="${this.sidebarId}" class="fixed inset-0 z-[100] flex justify-end pointer-events-none">
<!-- Backdrop -->
<div class="backdrop absolute inset-0 bg-slate-900/60 backdrop-blur-sm opacity-0 transition-opacity duration-300"></div>
const template = `
<div id="${this.sidebarId}" class="fixed inset-0 z-[100] flex justify-end pointer-events-none">
<!-- Backdrop -->
<div class="backdrop absolute inset-0 bg-slate-900/60 backdrop-blur-sm opacity-0 transition-opacity duration-300"></div>
<!-- Panel -->
<div class="panel w-full max-w-md bg-white shadow-2xl translate-x-full transition-transform duration-300 flex flex-col relative z-10">
<!-- Panel -->
<div class="panel w-full max-w-md bg-white shadow-2xl translate-x-full transition-transform duration-300 flex flex-col relative z-10">
<!-- Header -->
<div class="p-6 border-b border-gray-100 flex items-center justify-between bg-white">
<div>
<h2 class="text-2xl font-black text-slate-900 uppercase italic tracking-tighter">Ma Super <br><span class="text-[#f39e36]">Future Réservation</span></h2>
</div>
<button id="flow-reserve-close" class="p-2 hover:bg-gray-100 rounded-full transition-colors">
<svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
<!-- Header -->
<div class="p-6 border-b border-gray-100 flex items-center justify-between bg-white">
<div>
<h2 class="text-2xl font-black text-slate-900 uppercase italic tracking-tighter">Ma Super <br><span class="text-[#f39e36]">Future Réservation</span></h2>
</div>
<button id="flow-reserve-close" class="p-2 hover:bg-gray-100 rounded-full transition-colors">
<svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<!-- Content (Loader/List/Empty) -->
<div id="flow-reserve-content" class="flex-1 overflow-y-auto p-6 space-y-6 bg-gray-50/50 relative">
<!-- Content (Loader/List/Empty) -->
<div id="flow-reserve-content" class="flex-1 overflow-y-auto p-6 space-y-6 bg-gray-50/50 relative">
<!-- Content injected via JS -->
</div>
<!-- Footer -->
<div id="flow-reserve-footer" class="p-6 border-t border-gray-100 bg-white">
<!-- Content injected via JS -->
</div>
<!-- Footer -->
<div id="flow-reserve-footer" class="p-6 border-t border-gray-100 bg-white">
<!-- Content injected via JS -->
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', template);
</div>
`;
document.body.insertAdjacentHTML('beforeend', template);
// Bind events
const sidebar = document.getElementById(this.sidebarId);
// Bind events
const sidebar = document.getElementById(this.sidebarId);
const closeHandler = (e) => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
this.close();
};
const closeHandler = (e) => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
this.close();
};
sidebar.querySelector('.backdrop').addEventListener('click', closeHandler);
sidebar.querySelector('#flow-reserve-close').addEventListener('click', closeHandler);
}
sidebar.querySelector('.backdrop').addEventListener('click', closeHandler);
sidebar.querySelector('#flow-reserve-close').addEventListener('click', closeHandler);
}
async refreshContent() {
const container = document.getElementById('flow-reserve-content');
const footer = document.getElementById('flow-reserve-footer');
@@ -284,7 +284,6 @@ export class FlowReserve extends HTMLAnchorElement {
if (currentList.length !== initialLength) {
sessionStorage.setItem(this.storageKey, JSON.stringify(currentList));
window.dispatchEvent(new CustomEvent('cart:updated'));
// We don't recurse here to avoid infinite loops, but the UI will update next time or we could just use the returned 'products' which already excludes them.
console.warn('Certains produits ont été retirés car ils n\'existent plus:', data.unavailable_products_ids);
}
}

View File

@@ -0,0 +1,34 @@
<?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 Version20260209062832 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('CREATE TABLE product_promo (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, percentage DOUBLE PRECISION NOT NULL, date_start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, date_end TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, product_id INT NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX IDX_114FE1A74584665A ON product_promo (product_id)');
$this->addSql('ALTER TABLE product_promo ADD CONSTRAINT FK_114FE1A74584665A FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE product_promo DROP CONSTRAINT FK_114FE1A74584665A');
$this->addSql('DROP TABLE product_promo');
}
}

View File

@@ -0,0 +1,36 @@
<?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 Version20260209065100 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('CREATE TABLE promotion (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, percentage DOUBLE PRECISION NOT NULL, date_start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, date_end TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id))');
$this->addSql('ALTER TABLE product_promo DROP CONSTRAINT fk_114fe1a74584665a');
$this->addSql('DROP TABLE product_promo');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE product_promo (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, percentage DOUBLE PRECISION NOT NULL, date_start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, date_end TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, product_id INT NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE INDEX idx_114fe1a74584665a ON product_promo (product_id)');
$this->addSql('ALTER TABLE product_promo ADD CONSTRAINT fk_114fe1a74584665a FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('DROP TABLE promotion');
}
}

View File

@@ -403,7 +403,10 @@ class ReserverController extends AbstractController
$session->setState('created');
}
$session->setProducts($data ?? []);
// Save promos along with other data
$sessionData = $data ?? [];
// Ensure promos key exists if sent (it should be in $data if frontend sends it)
$session->setProducts($sessionData);
$user = $this->getUser();
if ($user instanceof Customer) {

81
src/Entity/Promotion.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
namespace App\Entity;
use App\Repository\PromotionRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: PromotionRepository::class)]
class Promotion
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column]
private ?float $percentage = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $dateStart = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $dateEnd = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getPercentage(): ?float
{
return $this->percentage;
}
public function setPercentage(float $percentage): static
{
$this->percentage = $percentage;
return $this;
}
public function getDateStart(): ?\DateTimeInterface
{
return $this->dateStart;
}
public function setDateStart(\DateTimeInterface $dateStart): static
{
$this->dateStart = $dateStart;
return $this;
}
public function getDateEnd(): ?\DateTimeInterface
{
return $this->dateEnd;
}
public function setDateEnd(\DateTimeInterface $dateEnd): static
{
$this->dateEnd = $dateEnd;
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Repository;
use App\Entity\Promotion;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Promotion>
*/
class PromotionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Promotion::class);
}
public function findActivePromotions(\DateTimeInterface $date): array
{
return $this->createQueryBuilder('p')
->where('p.dateStart <= :date')
->andWhere('p.dateEnd >= :date')
->setParameter('date', $date)
->getQuery()
->getResult();
}
}