From 034210d91daba7b993a8b16e2ca90d1b56194726 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Wed, 28 Jan 2026 15:59:49 +0100 Subject: [PATCH] =?UTF-8?q?```=20=E2=9C=A8=20feat(Formules.php):=20Ajoute?= =?UTF-8?q?=20relation=20OneToOne=20avec=20FormulesRestriction.=20?= =?UTF-8?q?=E2=9C=A8=20feat(Dashboard/FormulesController.php):=20G=C3=A8re?= =?UTF-8?q?=20restrictions=20formules=20et=20formulaire.=20=F0=9F=8E=A8=20?= =?UTF-8?q?refactor(template/formules):=20Am=C3=A9liore=20interface=20conf?= =?UTF-8?q?iguration=20restriction=20formule.=20=F0=9F=90=9B=20fix(assets/?= =?UTF-8?q?RepeatLine.js):=20Corrige=20r=C3=A9initialisation=20TomSelect?= =?UTF-8?q?=20et=20selects=20"Type".=20=E2=9C=A8=20feat(assets/initTomSele?= =?UTF-8?q?ct.js):=20G=C3=A8re=20cache=20options=20et=20init=20TomSelect.?= =?UTF-8?q?=20```?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/libs/RepeatLine.js | 32 ++--- assets/libs/initTomSelect.js | 48 ++------ migrations/Version20260128142222.php | 36 ++++++ migrations/Version20260128144635.php | 34 ++++++ src/Command/GitSyncLogCommand.php | 88 ++++++++------ .../Dashboard/FormulesController.php | 66 ++++++++++ src/Entity/Formules.php | 25 ++++ src/Entity/FormulesRestriction.php | 96 +++++++++++++++ .../FormulesRestrictionRepository.php | 43 +++++++ templates/dashboard/formules/config-free.twig | 114 +++++++++++++++++- templates/dashboard/formules/config-pack.twig | 3 +- 11 files changed, 491 insertions(+), 94 deletions(-) create mode 100644 migrations/Version20260128142222.php create mode 100644 migrations/Version20260128144635.php create mode 100644 src/Entity/FormulesRestriction.php create mode 100644 src/Repository/FormulesRestrictionRepository.php diff --git a/assets/libs/RepeatLine.js b/assets/libs/RepeatLine.js index 6515963..b46051b 100644 --- a/assets/libs/RepeatLine.js +++ b/assets/libs/RepeatLine.js @@ -42,39 +42,41 @@ export class RepeatLine extends HTMLDivElement { addRow() { if (!this.rowHTML || this.$refs.rows.children.length >= this.$props.maxRows) return; - // Création de la nouvelle ligne let newRow = this.createFromHTML(this.rowHTML); newRow.removeAttribute('id'); - // Nettoyage spécifique pour TomSelect avant insertion - // Si on clone une ligne qui avait déjà TomSelect, on reset le select newRow.querySelectorAll('select').forEach(select => { - // Supprimer les classes et éléments injectés par TomSelect si présents dans le template - select.classList.remove('tomselect', 'ts-hidden-visually'); - select.innerHTML = ''; - // Supprimer le wrapper TomSelect s'il a été cloné par erreur - const wrapper = select.nextElementSibling; - if (wrapper && wrapper.classList.contains('ts-wrapper')) { - wrapper.remove(); + // 1. Si c'est un select TomSelect (sans attribut 'is') + if (!select.hasAttribute('ds')) { + select.classList.remove('tomselect', 'ts-hidden-visually'); + select.innerHTML = ''; + + const wrapper = select.nextElementSibling; + if (wrapper && wrapper.classList.contains('ts-wrapper')) { + wrapper.remove(); + } + } + // 2. Si c'est votre select "Type" (avec l'attribut 'is') + else { + // On ne touche PAS au innerHTML pour garder les options (Structure, etc.) + select.value = ""; // On remet juste à zéro la sélection } }); this.setUpRow(newRow); this.$refs.rows.appendChild(newRow); - // Réinitialisation des valeurs - newRow.querySelectorAll('input,textarea,select').forEach(el => { + // Réinitialisation des autres champs + newRow.querySelectorAll('input,textarea').forEach(el => { el.value = ""; - if (el.tagName === 'SELECT') el.selectedIndex = 0; }); - // --- INITIALISATION TOMSELECT SUR LA NOUVELLE LIGNE --- + // Initialisation TomSelect uniquement sur ce qui doit l'être initTomSelect(newRow); this.updateFieldNames(); this.updateAddButton(); - // Focus sur le premier élément de la nouvelle ligne const firstInput = newRow.querySelector('input,textarea,select'); if (firstInput) firstInput.focus(); } diff --git a/assets/libs/initTomSelect.js b/assets/libs/initTomSelect.js index 1e792c5..917df8e 100644 --- a/assets/libs/initTomSelect.js +++ b/assets/libs/initTomSelect.js @@ -1,14 +1,17 @@ -// Cache pour éviter les requêtes HTTP répétitives import TomSelect from "tom-select"; +// Cache séparé pour éviter les conflits entre produits et options let productCache = null; +let optionsCache = null; /** * Initialise TomSelect sur un élément ou un groupe d'éléments */ export function initTomSelect(parent = document) { parent.querySelectorAll('select').forEach((el) => { - if (el.tomselect) return; + // --- CLAUSES DE GARDE --- + // On ignore si déjà initialisé OU si l'élément possède l'attribut "is" + if (el.tomselect || el.hasAttribute('ds')) return; // --- CONFIGURATION PRODUITS --- if (el.getAttribute('data-load') === "product") { @@ -19,34 +22,23 @@ export function initTomSelect(parent = document) { searchField: 'name', options: data, maxOptions: null, - // LORSQU'ON SÉLECTIONNE UN PRODUIT - // Dans admin.js, section onChange de TomSelect : onChange: (id) => { if (!id) return; - - // On s'assure de trouver le produit (id peut être string ou int) const product = data.find(p => String(p.id) === String(id)); - if (product) { - // On remonte au parent le plus proche (le bloc de ligne du devis) const row = el.closest('.form-repeater__row') || el.closest('fieldset'); if (!row) return; - // Ciblage précis des inputs const priceInput = row.querySelector('input[name*="[price_ht]"]'); const priceSupInput = row.querySelector('input[name*="[price_sup_ht]"]'); if (priceInput) { priceInput.value = product.price1day; - // Indispensable pour que d'autres scripts (calcul totaux) voient le changement priceInput.dispatchEvent(new Event('input', { bubbles: true })); - priceInput.dispatchEvent(new Event('change', { bubbles: true })); } - if (priceSupInput) { priceSupInput.value = product.priceSup; priceSupInput.dispatchEvent(new Event('input', { bubbles: true })); - priceSupInput.dispatchEvent(new Event('change', { bubbles: true })); } } }, @@ -59,16 +51,11 @@ export function initTomSelect(parent = document) {
J1: ${escape(data.price1day)}€ | Sup: ${escape(data.priceSup)}€
`, - item: (data, escape) => ` -
- - ${escape(data.name)} -
` + item: (data, escape) => `
${escape(data.name)}
` } }); }; - // Utilisation du cache ou fetch if (productCache) { setupSelect(productCache); } else { @@ -80,6 +67,7 @@ export function initTomSelect(parent = document) { }); } } + // --- CONFIGURATION OPTIONS --- else if (el.getAttribute('data-load') === "options") { const setupSelect = (data) => { new TomSelect(el, { @@ -88,12 +76,8 @@ export function initTomSelect(parent = document) { searchField: 'name', options: data, maxOptions: null, - // LORSQU'ON SÉLECTIONNE UN PRODUIT - // Dans admin.js, section onChange de TomSelect : onChange: (id) => { if (!id) return; - - // On s'assure de trouver le produit (id peut être string ou int) const product = data.find(p => String(p.id) === String(id)); const row = el.closest('.form-repeater__row') || el.closest('fieldset'); if (!row) return; @@ -101,11 +85,8 @@ export function initTomSelect(parent = document) { if (priceInput) { priceInput.value = product.price; - // Indispensable pour que d'autres scripts (calcul totaux) voient le changement priceInput.dispatchEvent(new Event('input', { bubbles: true })); - priceInput.dispatchEvent(new Event('change', { bubbles: true })); } - }, render: { option: (data, escape) => ` @@ -116,28 +97,23 @@ export function initTomSelect(parent = document) {
${escape(data.price)}€
`, - item: (data, escape) => ` -
- - ${escape(data.name)} -
` + item: (data, escape) => `
${escape(data.name)}
` } }); }; - // Utilisation du cache ou fetch - if (productCache) { - setupSelect(productCache); + if (optionsCache) { + setupSelect(optionsCache); } else { fetch("/crm/options/json") .then(r => r.json()) .then(data => { - productCache = data; + optionsCache = data; setupSelect(data); }); } } - // --- AUTRES SELECTS --- + // --- AUTRES SELECTS STANDARDS --- else { new TomSelect(el, { controlInput: null, diff --git a/migrations/Version20260128142222.php b/migrations/Version20260128142222.php new file mode 100644 index 0000000..12e72c5 --- /dev/null +++ b/migrations/Version20260128142222.php @@ -0,0 +1,36 @@ +addSql('CREATE TABLE formules_restriction (id SERIAL NOT NULL, formule_id INT DEFAULT NULL, nb_structure_max INT NOT NULL, restriction_config TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_D3436D392A68F4D1 ON formules_restriction (formule_id)'); + $this->addSql('COMMENT ON COLUMN formules_restriction.restriction_config IS \'(DC2Type:array)\''); + $this->addSql('ALTER TABLE formules_restriction ADD CONSTRAINT FK_D3436D392A68F4D1 FOREIGN KEY (formule_id) REFERENCES formules (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + 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 formules_restriction DROP CONSTRAINT FK_D3436D392A68F4D1'); + $this->addSql('DROP TABLE formules_restriction'); + } +} diff --git a/migrations/Version20260128144635.php b/migrations/Version20260128144635.php new file mode 100644 index 0000000..f811bfb --- /dev/null +++ b/migrations/Version20260128144635.php @@ -0,0 +1,34 @@ +addSql('ALTER TABLE formules_restriction ADD nb_alimentaire_max INT DEFAULT NULL'); + $this->addSql('ALTER TABLE formules_restriction ADD nb_barhums_max INT 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 formules_restriction DROP nb_alimentaire_max'); + $this->addSql('ALTER TABLE formules_restriction DROP nb_barhums_max'); + } +} diff --git a/src/Command/GitSyncLogCommand.php b/src/Command/GitSyncLogCommand.php index 132a8dc..4c5ae79 100644 --- a/src/Command/GitSyncLogCommand.php +++ b/src/Command/GitSyncLogCommand.php @@ -8,29 +8,45 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\Process; -// Utilisation des namespaces que tu as fournis +use Symfony\Component\HttpKernel\KernelInterface; // Pour le dossier projet +use Symfony\Contracts\HttpClient\HttpClientInterface; use GeminiAPI\Client; use GeminiAPI\Resources\Parts\TextPart; #[AsCommand( name: 'app:git-log-update', - description: 'Archive le dernier commit avec reformulation IA pour le client.', + description: 'Archive le dernier commit avec reformulation IA et notification Discord.', )] class GitSyncLogCommand extends Command { + private HttpClientInterface $httpClient; + private string $projectDir; + + // On injecte le Kernel pour le chemin du projet et HttpClient pour Discord + public function __construct(HttpClientInterface $httpClient, KernelInterface $kernel) + { + parent::__construct(); + $this->httpClient = $httpClient; + $this->projectDir = $kernel->getProjectDir(); + } + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $filePath = 'var/update.json'; - $projectDir = '/srv/app'; + + // Chemins dynamiques basés sur ProjectDir + $filePath = $this->projectDir . '/var/update.json'; + $discordWebhook = 'https://discord.com/api/webhooks/1447983279902031963/O6P5oHVHFe2t2MgjFmOW-tOVrvdLf3JQDPAj8snlgKIrfGc8uJQKAHgqRJJjyoSsFYCR'; // 1. Récupération des infos Git + // On utilise $this->projectDir pour le safe.directory $gitCmd = sprintf( 'git config --global --add safe.directory %s && git log -1 --format="%%s|%%ci|%%h"', - $projectDir + $this->projectDir ); $process = Process::fromShellCommandline($gitCmd); + $process->setWorkingDirectory($this->projectDir); // On force le dossier de travail $process->run(); if (!$process->isSuccessful()) { @@ -46,60 +62,41 @@ class GitSyncLogCommand extends Command // 2. Détermination du TYPE (feature, fix, optimise, new) $type = 'new'; $lowerMsg = strtolower($rawMessage); + if (preg_match('/(fix|bug|patch|correct)/', $lowerMsg)) $type = 'fix'; + elseif (preg_match('/(feat|add|create|nouveau|new)/', $lowerMsg)) $type = 'feature'; + elseif (preg_match('/(perf|opti|refactor|clean|speed)/', $lowerMsg)) $type = 'optimise'; - if (preg_match('/(fix|bug|patch|correct)/', $lowerMsg)) { - $type = 'fix'; - } elseif (preg_match('/(feat|add|create|nouveau|new)/', $lowerMsg)) { - $type = 'feature'; - } elseif (preg_match('/(perf|opti|refactor|clean|speed)/', $lowerMsg)) { - $type = 'optimise'; - } - - // 3. Vérification anti-doublon (Basée sur le Hash) + // 3. Vérification anti-doublon $data = []; if (file_exists($filePath)) { $data = json_decode(file_get_contents($filePath), true) ?? []; } if (!empty($data) && $data[0]['hash'] === $commitHash) { - $io->info("Le commit [$commitHash] est déjà dans le journal client."); + $io->info("Le commit [$commitHash] est déjà à jour."); return Command::SUCCESS; } - // 4. Appel IA Gemini-3-Pro-Preview pour la reformulation + // 4. Appel IA Gemini $friendlyMessage = $rawMessage; try { - // Ta clé API $client = new Client("AIzaSyDTPJERlUC47bcvhZU51Lwpqb1uxXS8SIg"); $model = 'gemini-3-pro-preview'; $prompt = "Tu es un expert en communication web pour Ludik Event. Ta mission est de transformer un message de commit technique en une note de mise à jour élégante pour ton client. - MESSAGE TECHNIQUE : \"$rawMessage\" + DIRECTIVES : Court, positif, pas de 'Voici la phrase', uniquement le résultat final."; - DIRECTIVES : - 1. Reformule pour un propriétaire de site non-technique. - 2. Sois court, positif et rassurant. - 3. Ne commence JAMAIS par 'Voici la phrase' ou 'Mise à jour'. - 4. Donne uniquement le texte final prêt à être affiché. - - RÉSULTAT ATTENDU :"; - - $response = $client->withV1BetaVersion()->generativeModel($model)->generateContent( - new TextPart($prompt) - ); - - // Adaptation selon la structure de retour du SDK - $aiText = $response->text(); - if ($aiText) { - $friendlyMessage = trim($aiText); + $response = $client->withV1BetaVersion()->generativeModel($model)->generateContent(new TextPart($prompt)); + if ($response->text()) { + $friendlyMessage = trim($response->text()); } } catch (\Exception $e) { - $io->warning("L'IA n'a pas pu traiter le message. Utilisation du texte brut."); + $io->warning("L'IA n'a pas pu traiter le message."); } - // 5. Création de l'entrée JSON + // 5. Mise à jour du fichier JSON $newEntry = [ 'type' => $type, 'message' => $friendlyMessage, @@ -107,18 +104,29 @@ class GitSyncLogCommand extends Command 'hash' => $commitHash ]; - // 6. Sauvegarde et rotation (5 max) array_unshift($data, $newEntry); $data = array_slice($data, 0, 6); - if (!is_dir('var')) { - mkdir('var', 0777, true); + $varDir = $this->projectDir . '/var'; + if (!is_dir($varDir)) { + mkdir($varDir, 0777, true); } file_put_contents($filePath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - $io->success("Journal client mis à jour avec succès (Type: $type)."); + // 6. Envoi Discord + try { + $discordMessage = "📢 **Mise à jour sera prochaine mise en ligne sur votre intranet**\n\n> " . $friendlyMessage; + $this->httpClient->request('POST', $discordWebhook, [ + 'json' => ['content' => $discordMessage] + ]); + $io->note("Notification Discord envoyée."); + } catch (\Exception $e) { + $io->error("Erreur Discord : " . $e->getMessage()); + } + + $io->success("Journal client mis à jour avec succès."); return Command::SUCCESS; } } diff --git a/src/Controller/Dashboard/FormulesController.php b/src/Controller/Dashboard/FormulesController.php index 69f66ee..8b48919 100644 --- a/src/Controller/Dashboard/FormulesController.php +++ b/src/Controller/Dashboard/FormulesController.php @@ -5,6 +5,7 @@ namespace App\Controller\Dashboard; use App\Entity\Formules; use App\Entity\FormulesOptionsInclus; use App\Entity\FormulesProductInclus; +use App\Entity\FormulesRestriction; use App\Entity\Options; use App\Entity\Product; use App\Entity\ProductDoc; @@ -59,6 +60,8 @@ class FormulesController extends AbstractController if ($this->isCsrfTokenValid('delete' . $formules->getId(), $request->query->get('_token'))) { $nomFormule = $formules->getName(); + if($formules->getFormulesRestriction() instanceof FormulesRestriction) + $entityManager->remove($formules->getFormulesRestriction()); // 3. Suppression foreach ($formules->getFormulesProductIncluses() as $formulesProductInclus) $entityManager->remove($formulesProductInclus); @@ -88,6 +91,15 @@ class FormulesController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $entityManager->persist($formules); + if($formules->getType() == "free") { + $formuleRestriction = new FormulesRestriction(); + $formuleRestriction->setFormule($formules); + $formuleRestriction->setNbStructureMax(0); + $formuleRestriction->setNbAlimentaireMax(0); + $formuleRestriction->setNbBarhumsMax(0); + $formuleRestriction->setRestrictionConfig([]); + $entityManager->persist($formuleRestriction); + } $entityManager->flush(); $appLogger->record('CREATED', "Création de la formule : " . $formules->getName()); $this->addFlash("success", "La formule a été créée avec succès."); @@ -159,6 +171,17 @@ class FormulesController extends AbstractController return $this->redirectToRoute('app_crm_formules_view', ['id' => $formules->getId()]); } + if ($request->isMethod('POST') && $request->request->has('rest')) { + $rest = $request->request->all('rest'); + $f = $formules->getFormulesRestriction()->setRestrictionConfig($rest); + $entityManager->persist($f); + $entityManager->flush(); + + $appLogger->record('UPDATE', "Mise à jour des restriction pour : " . $formules->getName()); + $this->addFlash("success", "Les restriction ont été mis à jour."); + + return $this->redirectToRoute('app_crm_formules_view', ['id' => $formules->getId()]); + } // 2. GESTION DES PRIX (Formulaire Manuel price[]) // On vérifie si le tableau 'price' existe dans la requête POST @@ -183,8 +206,41 @@ class FormulesController extends AbstractController $form = $this->createForm(FormulesType::class, $formules); $form->handleRequest($request); + + if ($request->isMethod('POST') && $request->request->has('nbStructureMax')) { + // Vérification du type de formule + if ($formules->getType() === "free") { + $nbStructureMax = $request->request->get('nbStructureMax'); + $nbBarhumsMax = $request->request->get('nbBarhumsMax'); + $nbAlimentaireMax = $request->request->get('nbAlimentaireMax'); + $rc = $formules->getFormulesRestriction(); + + // Si la restriction existe, on met à jour la valeur + if ($rc) { + $rc->setNbStructureMax((int)$nbStructureMax); + $rc->setNbBarhumsMax((int)$nbBarhumsMax); + $rc->setNbAlimentaireMax((int)$nbAlimentaireMax); + + // Persistance des modifications + $entityManager->persist($rc); + $entityManager->flush(); + + // Log de l'activité + $appLogger->record('UPDATE', "Modification de la formule (restriction) : " . $formules->getName()); + + $this->addFlash("success", "La restriction de la formule a été modifiée avec succès."); + } else { + $this->addFlash("error", "Aucune restriction trouvée pour cette formule."); + } + } else { + $this->addFlash("warning", "Le nombre de structures max n'est modifiable que pour les formules gratuites."); + } + + return $this->redirectToRoute('app_crm_formules_view', ['id' => $formules->getId()]); + } if ($form->isSubmitted() && $form->isValid()) { $entityManager->persist($formules); + // 2. Sauvegarde globale $entityManager->flush(); $appLogger->record('UPDATE', "Modification de la formule (infos) : " . $formules->getName()); @@ -211,12 +267,22 @@ class FormulesController extends AbstractController $options[$key]['product'] = $fc->getName(); $options[$key]['id'] = $fc->getId(); } + $restriction =[ + [ + 'type' => 'structure', + 'product' => '' + ] + ]; + if(!empty($formules->getFormulesRestriction()->getRestrictionConfig())){ + $restriction = $formules->getFormulesRestriction()->getRestrictionConfig(); + } return $this->render('dashboard/formules/view.twig', [ 'formule' => $formules, 'form' => $form->createView(), 'type' => $formules->getType(), 'lines' => $lines, 'option' => $options, + 'restriction' => $restriction, ]); } diff --git a/src/Entity/Formules.php b/src/Entity/Formules.php index 7487593..d348421 100644 --- a/src/Entity/Formules.php +++ b/src/Entity/Formules.php @@ -69,6 +69,9 @@ class Formules #[ORM\OneToMany(targetEntity: FormulesOptionsInclus::class, mappedBy: 'formule')] private Collection $formulesOptionsIncluses; + #[ORM\OneToOne(mappedBy: 'formule', cascade: ['persist', 'remove'])] + private ?FormulesRestriction $formulesRestriction = null; + public function __construct() { $this->formulesProductIncluses = new ArrayCollection(); @@ -307,4 +310,26 @@ class Formules return $this; } + + public function getFormulesRestriction(): ?FormulesRestriction + { + return $this->formulesRestriction; + } + + public function setFormulesRestriction(?FormulesRestriction $formulesRestriction): static + { + // unset the owning side of the relation if necessary + if ($formulesRestriction === null && $this->formulesRestriction !== null) { + $this->formulesRestriction->setFormule(null); + } + + // set the owning side of the relation if necessary + if ($formulesRestriction !== null && $formulesRestriction->getFormule() !== $this) { + $formulesRestriction->setFormule($this); + } + + $this->formulesRestriction = $formulesRestriction; + + return $this; + } } diff --git a/src/Entity/FormulesRestriction.php b/src/Entity/FormulesRestriction.php new file mode 100644 index 0000000..8e81c6c --- /dev/null +++ b/src/Entity/FormulesRestriction.php @@ -0,0 +1,96 @@ +id; + } + + public function getFormule(): ?Formules + { + return $this->formule; + } + + public function setFormule(?Formules $formule): static + { + $this->formule = $formule; + + return $this; + } + + public function getNbStructureMax(): ?int + { + return $this->nbStructureMax; + } + + public function setNbStructureMax(int $nbStructureMax): static + { + $this->nbStructureMax = $nbStructureMax; + + return $this; + } + + public function getRestrictionConfig(): ?array + { + return $this->restrictionConfig; + } + + public function setRestrictionConfig(?array $restrictionConfig): static + { + $this->restrictionConfig = $restrictionConfig; + + return $this; + } + + public function getNbAlimentaireMax(): ?int + { + return $this->nbAlimentaireMax; + } + + public function setNbAlimentaireMax(?int $nbAlimentaireMax): static + { + $this->nbAlimentaireMax = $nbAlimentaireMax; + + return $this; + } + + public function getNbBarhumsMax(): ?int + { + return $this->nbBarhumsMax; + } + + public function setNbBarhumsMax(?int $nbBarhumsMax): static + { + $this->nbBarhumsMax = $nbBarhumsMax; + + return $this; + } +} diff --git a/src/Repository/FormulesRestrictionRepository.php b/src/Repository/FormulesRestrictionRepository.php new file mode 100644 index 0000000..c7ac51f --- /dev/null +++ b/src/Repository/FormulesRestrictionRepository.php @@ -0,0 +1,43 @@ + + */ +class FormulesRestrictionRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, FormulesRestriction::class); + } + +// /** +// * @return FormulesRestriction[] Returns an array of FormulesRestriction objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('f') +// ->andWhere('f.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('f.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?FormulesRestriction +// { +// return $this->createQueryBuilder('f') +// ->andWhere('f.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/templates/dashboard/formules/config-free.twig b/templates/dashboard/formules/config-free.twig index c0a8b4d..1b4b491 100644 --- a/templates/dashboard/formules/config-free.twig +++ b/templates/dashboard/formules/config-free.twig @@ -1 +1,113 @@ -dazdazdazdaz +
+
+ +
+ {# STRUCTURES #} +
+ +
+
+ + + +
+ +
+

Limite totale de structures gonflables ou rigides.

+
+ + {# ALIMENTAIRE #} +
+ +
+
+ + + +
+ +
+

Machines à barbe à papa, popcorn, buffets, etc.

+
+ + {# BARNUMS #} +
+ +
+
+ + + +
+ +
+

Tentes de réception et protections extérieures.

+
+ +
+ +
+
+
+ +
+
+
+

Détail des produits autorisés

+
+ +
    + {% for key,line in restriction %} +
  1. +
    +
    + + {# 1. PRODUIT #} +
    + +
    + +
    + + +
    +
    +
    + + {# 2. TYPE #} +
    + + +
    + + {# 3. SUPPRIMER #} +
    + +
    +
    +
    +
  2. + {% endfor %} +
+ +
+ + + +
+
+
diff --git a/templates/dashboard/formules/config-pack.twig b/templates/dashboard/formules/config-pack.twig index 179c625..7773380 100644 --- a/templates/dashboard/formules/config-pack.twig +++ b/templates/dashboard/formules/config-pack.twig @@ -77,8 +77,7 @@ -
+

Détail des options inclus