From 38f8762efe453996acfc522859e814e7034a50dd Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Fri, 6 Feb 2026 18:04:13 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(templates-points-controle):=20?= =?UTF-8?q?Ajoute=20la=20gestion=20et=20l'application=20de=20mod=C3=A8les?= =?UTF-8?q?=20de=20points=20de=20contr=C3=B4le=20aux=20produits.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/admin.js | 2 + assets/libs/TemplateApply.js | 26 ++++ migrations/Version20260206170047.php | 69 +++++++++++ .../Dashboard/CustomerController.php | 1 + .../Dashboard/ProductController.php | 7 +- .../TemplatePointControleController.php | 111 ++++++++++++++++++ src/Entity/TemplatePointControle.php | 51 ++++++++ .../TemplatePointControleRepository.php | 18 +++ templates/dashboard/base.twig | 23 ++-- templates/dashboard/products/add.twig | 24 ++++ .../template_point_controle/edit.twig | 36 ++++++ .../template_point_controle/index.twig | 54 +++++++++ .../template_point_controle/new.twig | 37 ++++++ templates/revervation/base.twig | 2 +- 14 files changed, 448 insertions(+), 13 deletions(-) create mode 100644 assets/libs/TemplateApply.js create mode 100644 migrations/Version20260206170047.php create mode 100644 src/Controller/Dashboard/TemplatePointControleController.php create mode 100644 src/Entity/TemplatePointControle.php create mode 100644 src/Repository/TemplatePointControleRepository.php create mode 100644 templates/dashboard/template_point_controle/edit.twig create mode 100644 templates/dashboard/template_point_controle/index.twig create mode 100644 templates/dashboard/template_point_controle/new.twig diff --git a/assets/admin.js b/assets/admin.js index 3106034..07304ab 100644 --- a/assets/admin.js +++ b/assets/admin.js @@ -14,6 +14,7 @@ import PlaningLogestics from "./libs/PlaningLogestics.js"; import {SortableReorder} from "./libs/SortableReorder.js"; import { StripeCommissionCalculator } from "./libs/StripeCommissionCalculator.js"; import { ProductAddOption } from "./libs/ProductAddOption.js"; +import { TemplateApply } from "./libs/TemplateApply.js"; import { LeafletMap } from "./tools/LeafletMap.js"; // --- CONFIGURATION SENTRY --- @@ -45,6 +46,7 @@ const registerCustomElements = () => { { name: 'crm-editor', class: CrmEditor, extends: 'textarea' }, { name: 'stripe-commission-calculator', class: StripeCommissionCalculator, extends: 'div' }, { name: 'product-add-option', class: ProductAddOption, extends: 'button' }, + { name: 'template-apply', class: TemplateApply, extends: 'button' }, { name: 'leaflet-map', class: LeafletMap } ]; diff --git a/assets/libs/TemplateApply.js b/assets/libs/TemplateApply.js new file mode 100644 index 0000000..bf4fefa --- /dev/null +++ b/assets/libs/TemplateApply.js @@ -0,0 +1,26 @@ +export class TemplateApply extends HTMLButtonElement { + constructor() { + super(); + this.addEventListener('click', this.apply.bind(this)); + } + + apply() { + const selectorId = this.getAttribute('data-selector'); + const urlPattern = this.getAttribute('data-url-pattern'); + const selector = document.querySelector(selectorId); + + if (!selector) return; + + const templateId = selector.value; + if (!templateId) return; + + if (!confirm('Appliquer ce modèle ? Cela ajoutera les points de contrôle au produit.')) return; + + const form = document.createElement('form'); + form.method = 'POST'; + form.action = urlPattern.replace('TEMPLATE_ID', templateId); + + document.body.appendChild(form); + form.submit(); + } +} diff --git a/migrations/Version20260206170047.php b/migrations/Version20260206170047.php new file mode 100644 index 0000000..65ba669 --- /dev/null +++ b/migrations/Version20260206170047.php @@ -0,0 +1,69 @@ +addSql('CREATE TABLE template_point_controle (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, points JSON NOT NULL, PRIMARY KEY (id))'); + $this->addSql('ALTER INDEX idx_9591436be30da2f7 RENAME TO IDX_7268396CBE3DB2B7'); + $this->addSql('ALTER INDEX idx_8b27c52bbe30da2f7 RENAME TO IDX_8B27C52BBE3DB2B7'); + $this->addSql('COMMENT ON COLUMN etat_lieux.updated_at IS \'\''); + $this->addSql('ALTER INDEX idx_d71603599b6b5fba RENAME TO IDX_D8D384179B6B5FBA'); + $this->addSql('ALTER TABLE etat_lieux_comment ALTER id DROP DEFAULT'); + $this->addSql('ALTER TABLE etat_lieux_comment ALTER id ADD GENERATED BY DEFAULT AS IDENTITY'); + $this->addSql('COMMENT ON COLUMN etat_lieux_comment.created_at IS \'\''); + $this->addSql('ALTER INDEX idx_etat_lieux_comment RENAME TO IDX_C7341FDB3F1DAE3C'); + $this->addSql('ALTER TABLE etat_lieux_file ALTER id DROP DEFAULT'); + $this->addSql('ALTER TABLE etat_lieux_file ALTER id ADD GENERATED BY DEFAULT AS IDENTITY'); + $this->addSql('COMMENT ON COLUMN etat_lieux_file.created_at IS \'\''); + $this->addSql('ALTER INDEX idx_5f7e1c87d6f39243 RENAME TO IDX_A76FE88B3F1DAE3C'); + $this->addSql('ALTER TABLE etat_lieux_point_control ALTER id DROP DEFAULT'); + $this->addSql('ALTER TABLE etat_lieux_point_control ALTER id ADD GENERATED BY DEFAULT AS IDENTITY'); + $this->addSql('ALTER INDEX idx_etat_lieux_point_control RENAME TO IDX_84DA2663F1DAE3C'); + $this->addSql('ALTER INDEX idx_88755657be30da2f7 RENAME TO IDX_263E7C9FBE3DB2B7'); + $this->addSql('ALTER TABLE product_point_controll ALTER id DROP DEFAULT'); + $this->addSql('ALTER TABLE product_point_controll ALTER id ADD GENERATED BY DEFAULT AS IDENTITY'); + $this->addSql('ALTER INDEX idx_9e3c8f8f4584665a RENAME TO IDX_B6E9A5844584665A'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE template_point_controle'); + $this->addSql('ALTER INDEX idx_7268396cbe3db2b7 RENAME TO idx_9591436be30da2f7'); + $this->addSql('ALTER INDEX idx_8b27c52bbe3db2b7 RENAME TO idx_8b27c52bbe30da2f7'); + $this->addSql('COMMENT ON COLUMN etat_lieux.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER INDEX idx_d8d384179b6b5fba RENAME TO idx_d71603599b6b5fba'); + $this->addSql('ALTER TABLE etat_lieux_comment ALTER id SET DEFAULT nextval(\'etat_lieux_comment_id_seq\'::regclass)'); + $this->addSql('ALTER TABLE etat_lieux_comment ALTER id DROP IDENTITY'); + $this->addSql('COMMENT ON COLUMN etat_lieux_comment.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER INDEX idx_c7341fdb3f1dae3c RENAME TO idx_etat_lieux_comment'); + $this->addSql('ALTER TABLE etat_lieux_file ALTER id SET DEFAULT nextval(\'etat_lieux_file_id_seq\'::regclass)'); + $this->addSql('ALTER TABLE etat_lieux_file ALTER id DROP IDENTITY'); + $this->addSql('COMMENT ON COLUMN etat_lieux_file.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER INDEX idx_a76fe88b3f1dae3c RENAME TO idx_5f7e1c87d6f39243'); + $this->addSql('ALTER TABLE etat_lieux_point_control ALTER id SET DEFAULT nextval(\'etat_lieux_point_control_id_seq\'::regclass)'); + $this->addSql('ALTER TABLE etat_lieux_point_control ALTER id DROP IDENTITY'); + $this->addSql('ALTER INDEX idx_84da2663f1dae3c RENAME TO idx_etat_lieux_point_control'); + $this->addSql('ALTER INDEX idx_263e7c9fbe3db2b7 RENAME TO idx_88755657be30da2f7'); + $this->addSql('ALTER TABLE product_point_controll ALTER id SET DEFAULT nextval(\'product_point_controll_id_seq\'::regclass)'); + $this->addSql('ALTER TABLE product_point_controll ALTER id DROP IDENTITY'); + $this->addSql('ALTER INDEX idx_b6e9a5844584665a RENAME TO idx_9e3c8f8f4584665a'); + } +} diff --git a/src/Controller/Dashboard/CustomerController.php b/src/Controller/Dashboard/CustomerController.php index 2bd3b16..f8cd35f 100644 --- a/src/Controller/Dashboard/CustomerController.php +++ b/src/Controller/Dashboard/CustomerController.php @@ -92,6 +92,7 @@ class CustomerController extends AbstractController } catch (\Exception $e) { $this->addFlash('warning', 'Erreur création Stripe : ' . $e->getMessage()); } + $this->em->flush(); $this->appLogger->record('CREATE', sprintf( 'Nouveau client créé : %s %s (Type: %s)', diff --git a/src/Controller/Dashboard/ProductController.php b/src/Controller/Dashboard/ProductController.php index db097a3..5691eb4 100644 --- a/src/Controller/Dashboard/ProductController.php +++ b/src/Controller/Dashboard/ProductController.php @@ -121,6 +121,7 @@ class ProductController extends AbstractController $em->flush(); $stripe->createProduct($product); + $em->flush(); $logger->record('CREATE', "Nouveau produit : [{$product->getRef()}] {$product->getName()}"); $bus->dispatch(new DumpSitemapMessage()); @@ -132,7 +133,7 @@ class ProductController extends AbstractController } #[Route(path: '/crm/products/edit/{id}', name: 'app_crm_product_edit', methods: ['GET', 'POST'])] - public function productEdit(Product $product, EntityManagerInterface $em, AppLogger $logger, Request $request, Client $stripe, \App\Repository\ProductBlockedRepository $blockedRepo, \App\Repository\ProductReserveRepository $reserveRepo, OptionsRepository $optionsRepo): Response + public function productEdit(Product $product, EntityManagerInterface $em, AppLogger $logger, Request $request, Client $stripe, \App\Repository\ProductBlockedRepository $blockedRepo, \App\Repository\ProductReserveRepository $reserveRepo, OptionsRepository $optionsRepo, \App\Repository\TemplatePointControleRepository $templateRepo): Response { // 0. Toggle Publish if ($request->query->get('act') === 'togglePublish') { @@ -346,7 +347,8 @@ class ProductController extends AbstractController 'formBlocked' => $formBlocked->createView(), 'product' => $product, 'is_edit' => true, - 'optionsList' => $optionsRepo->findBy([], ['name' => 'ASC']) + 'optionsList' => $optionsRepo->findBy([], ['name' => 'ASC']), + 'templates' => $templateRepo->findAll() ]); } #[Route(path: '/crm/products/delete/{id}', name: 'app_crm_product_delete', methods: ['POST'])] @@ -378,6 +380,7 @@ class ProductController extends AbstractController $em->flush(); $stripe->createOptions($option); + $em->flush(); $logger->record('CREATE', "Nouvelle option : {$option->getName()}"); $this->addFlash('success', "L'option {$option->getName()} a été ajoutée."); diff --git a/src/Controller/Dashboard/TemplatePointControleController.php b/src/Controller/Dashboard/TemplatePointControleController.php new file mode 100644 index 0000000..8fbce3d --- /dev/null +++ b/src/Controller/Dashboard/TemplatePointControleController.php @@ -0,0 +1,111 @@ +render('dashboard/template_point_controle/index.twig', [ + 'templates' => $templatePointControleRepository->findAll(), + ]); + } + + #[Route('/new', name: 'app_template_point_controle_new', methods: ['GET', 'POST'])] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $template = new TemplatePointControle(); + + if ($request->isMethod('POST')) { + $name = $request->request->get('name'); + $pointsStr = $request->request->get('points'); + + $points = array_filter(array_map('trim', explode(" +", $pointsStr))); + + $template->setName($name); + $template->setPoints(array_values($points)); + + $entityManager->persist($template); + $entityManager->flush(); + + return $this->redirectToRoute('app_template_point_controle_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('dashboard/template_point_controle/new.twig', [ + 'template' => $template, + ]); + } + + #[Route('/{id}/edit', name: 'app_template_point_controle_edit', methods: ['GET', 'POST'])] + public function edit(Request $request, TemplatePointControle $template, EntityManagerInterface $entityManager): Response + { + if ($request->isMethod('POST')) { + $name = $request->request->get('name'); + $pointsStr = $request->request->get('points'); + + $points = array_filter(array_map('trim', explode(" +", $pointsStr))); + + $template->setName($name); + $template->setPoints(array_values($points)); + + $entityManager->flush(); + + return $this->redirectToRoute('app_template_point_controle_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('dashboard/template_point_controle/edit.twig', [ + 'template' => $template, + ]); + } + + #[Route('/{id}', name: 'app_template_point_controle_delete', methods: ['POST'])] + public function delete(Request $request, TemplatePointControle $template, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$template->getId(), $request->request->get('_token'))) { + $entityManager->remove($template); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_template_point_controle_index', [], Response::HTTP_SEE_OTHER); + } + + #[Route('/apply/{id}/product/{productId}', name: 'app_template_point_controle_apply', methods: ['POST'])] + public function apply(TemplatePointControle $template, int $productId, EntityManagerInterface $entityManager): Response + { + $product = $entityManager->getRepository(Product::class)->find($productId); + + if (!$product) { + throw $this->createNotFoundException('Product not found'); + } + + foreach ($template->getPoints() as $pointName) { + $point = new ProductPointControll(); + $point->setName($pointName); + $point->setProduct($product); + $entityManager->persist($point); + } + + $entityManager->flush(); + + $this->addFlash('success', 'Template appliqué avec succès !'); + + return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); + } +} diff --git a/src/Entity/TemplatePointControle.php b/src/Entity/TemplatePointControle.php new file mode 100644 index 0000000..4b9e5c8 --- /dev/null +++ b/src/Entity/TemplatePointControle.php @@ -0,0 +1,51 @@ +id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getPoints(): array + { + return $this->points; + } + + public function setPoints(array $points): static + { + $this->points = $points; + + return $this; + } +} diff --git a/src/Repository/TemplatePointControleRepository.php b/src/Repository/TemplatePointControleRepository.php new file mode 100644 index 0000000..e54f76b --- /dev/null +++ b/src/Repository/TemplatePointControleRepository.php @@ -0,0 +1,18 @@ + + */ +class TemplatePointControleRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, TemplatePointControle::class); + } +} diff --git a/templates/dashboard/base.twig b/templates/dashboard/base.twig index e16e53a..e7140da 100644 --- a/templates/dashboard/base.twig +++ b/templates/dashboard/base.twig @@ -14,7 +14,7 @@ {% if not is_online() %}
- Votre site internet n'est actuellement pas en ligne. Aucun référencement n'est possible. Pour demander sa mise en ligne, rendez-vous dans votre intranet pour effectuer la demande d'activation. + Votre site internet n'est actuellement pas en ligne. Aucun référencement n'est possible et aucun paiement ne sera possible. Pour demander sa mise en ligne, rendez-vous dans votre intranet pour effectuer la demande d'activation.
{% endif %} @@ -45,14 +45,17 @@ {% endmacro %} {% import _self as menu %} - {{ menu.nav_link(path('app_crm'), 'Dashboard', '', 'app_crm') }} - {{ menu.nav_link(path('app_crm_reservation'), 'Planing de réservation', '', 'app_crm_reservation') }} - {{ menu.nav_link(path('app_crm_product'), 'Produits', '', 'app_crm_product') }} - {{ menu.nav_link(path('app_crm_formules'), 'Formules', '', 'app_crm_formules') }} - {{ menu.nav_link(path('app_crm_facture'), 'Facture', '', 'app_crm_facture') }} - {{ menu.nav_link(path('app_crm_customer'), 'Clients', '', 'app_clients') }} - {{ menu.nav_link(path('app_crm_prestataire'), 'Prestataires', '', 'app_crm_prestataire') }} - + {{ menu.nav_link(path('app_crm'), 'Dashboard', '', 'app_crm') }} + {{ menu.nav_link(path('app_crm_reservation'), 'Planing de réservation', '', 'app_crm_reservation') }} + {{ menu.nav_link(path('app_template_point_controle_index'), 'Modèles de contrôle', '', 'app_template_point_controle_index') }} + {{ menu.nav_link(path('app_crm_product'), 'Produits', '', 'app_crm_product') }} + {{ menu.nav_link(path('app_crm_formules'), 'Formules', '', 'app_crm_formules') }} + {{ menu.nav_link(path('app_crm_facture'), 'Facture', '', 'app_crm_facture') }} + {{ menu.nav_link(path('app_crm_customer'), 'Clients', '', 'app_crm_customer') }} + {{ menu.nav_link(path('app_crm_devis'), 'Devis', '', 'app_crm_devis') }} + {{ menu.nav_link(path('app_crm_contrats'), 'Contrat de location', '', 'app_crm_contrats') }} + {{ menu.nav_link(path('app_crm_prestataire'), 'Prestataires', '', 'app_crm_prestataire') }} + {% set pendingCount = getPendingOrderSessionCount() %}
@@ -223,4 +226,4 @@
- + \ No newline at end of file diff --git a/templates/dashboard/products/add.twig b/templates/dashboard/products/add.twig index 2bff79e..af2aa09 100644 --- a/templates/dashboard/products/add.twig +++ b/templates/dashboard/products/add.twig @@ -618,6 +618,30 @@ Points de Contrôle (Entretien) + {# TEMPLATE SELECTION #} + {% if templates is defined and templates|length > 0 %} +
+ +
+ + +
+
+ {% endif %} +
{% for point in product.productPointControlls %}
diff --git a/templates/dashboard/template_point_controle/edit.twig b/templates/dashboard/template_point_controle/edit.twig new file mode 100644 index 0000000..ce6d9a2 --- /dev/null +++ b/templates/dashboard/template_point_controle/edit.twig @@ -0,0 +1,36 @@ +{% extends 'dashboard/base.twig' %} + +{% block title %}Éditer le modèle{% endblock %} + +{% block body %} +
+ + +
+
+
+ + +
+ +
+ + +

Saisissez chaque point de contrôle sur une nouvelle ligne.

+
+ +
+ +
+
+
+
+{% endblock %} diff --git a/templates/dashboard/template_point_controle/index.twig b/templates/dashboard/template_point_controle/index.twig new file mode 100644 index 0000000..86cab9a --- /dev/null +++ b/templates/dashboard/template_point_controle/index.twig @@ -0,0 +1,54 @@ +{% extends 'dashboard/base.twig' %} + +{% block title %}Modèles de points de contrôle{% endblock %} + +{% block body %} +
+
+

Modèles de points de contrôle

+ + Nouveau modèle + +
+ +
+ + + + + + + + + + {% for template in templates %} + + + + + + {% else %} + + + + {% endfor %} + +
NomPointsActions
{{ template.name }} +
    + {% for point in template.points|slice(0, 3) %} +
  • {{ point }}
  • + {% endfor %} + {% if template.points|length > 3 %} +
  • + {{ template.points|length - 3 }} autres...
  • + {% endif %} +
+
+ Éditer +
+ + +
+
Aucun modèle trouvé.
+
+
+{% endblock %} diff --git a/templates/dashboard/template_point_controle/new.twig b/templates/dashboard/template_point_controle/new.twig new file mode 100644 index 0000000..6cdc5c4 --- /dev/null +++ b/templates/dashboard/template_point_controle/new.twig @@ -0,0 +1,37 @@ +{% extends 'dashboard/base.twig' %} + +{% block title %}Nouveau modèle de points de contrôle{% endblock %} + +{% block body %} +
+
+

Nouveau modèle

+ Retour +
+ +
+
+
+ + +
+ +
+ + +

Saisissez chaque point de contrôle sur une nouvelle ligne.

+
+ +
+ +
+
+
+
+{% endblock %} diff --git a/templates/revervation/base.twig b/templates/revervation/base.twig index bd1dd02..cf3fbee 100644 --- a/templates/revervation/base.twig +++ b/templates/revervation/base.twig @@ -85,7 +85,7 @@ {% if not is_online() %}
- Votre site internet n'est actuellement pas en ligne. Aucun référencement n'est possible. Pour demander sa mise en ligne, rendez-vous dans votre intranet pour effectuer la demande d'activation. + Votre site internet n'est actuellement pas en ligne. Aucun référencement n'est possible et aucun paiement ne sera possible. Pour demander sa mise en ligne, rendez-vous dans votre intranet pour effectuer la demande d'activation.
{% endif %}