From bd0efe258e09c2d90f5262cf2b7c3b56436b1ee6 Mon Sep 17 00:00:00 2001 From: Nathan Boiron Date: Sat, 9 May 2026 22:58:27 +0200 Subject: [PATCH 1/2] Gestion de produits pour les devis --- app/config/packages/backoffice_menu.yaml | 8 +++ app/config/routing/admin_accounting.yml | 4 ++ .../routing/admin_accounting/produits.yml | 19 ++++++ ...0508153741_create_compta_produit_table.php | 19 ++++++ .../AppBundle/Accounting/Entity/Produit.php | 32 ++++++++++ .../Entity/Repository/ProduitRepository.php | 31 +++++++++ .../AppBundle/Accounting/Form/ProduitType.php | 63 ++++++++++++++++++ .../Accounting/Produit/AddProduitAction.php | 41 ++++++++++++ .../Produit/DeleteProduitAction.php | 34 ++++++++++ .../Accounting/Produit/EditProduitAction.php | 40 ++++++++++++ .../Accounting/Produit/ListProduitAction.php | 27 ++++++++ .../Quotation/AddQuotationAction.php | 4 +- .../Quotation/EditQuotationAction.php | 3 + .../admin/accounting/produit/form.html.twig | 45 +++++++++++++ .../admin/accounting/produit/list.html.twig | 64 +++++++++++++++++++ .../quotation/_javascript.html.twig | 35 ++++++++-- .../admin/accounting/quotation/form.html.twig | 57 ++++++++++++++++- .../Admin/Tresorerie/DevisFactures.feature | 2 + .../Admin/Tresorerie/Produits.feature | 37 +++++++++++ 19 files changed, 557 insertions(+), 8 deletions(-) create mode 100644 app/config/routing/admin_accounting/produits.yml create mode 100644 db/migrations/20260508153741_create_compta_produit_table.php create mode 100644 sources/AppBundle/Accounting/Entity/Produit.php create mode 100644 sources/AppBundle/Accounting/Entity/Repository/ProduitRepository.php create mode 100644 sources/AppBundle/Accounting/Form/ProduitType.php create mode 100644 sources/AppBundle/Controller/Admin/Accounting/Produit/AddProduitAction.php create mode 100644 sources/AppBundle/Controller/Admin/Accounting/Produit/DeleteProduitAction.php create mode 100644 sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php create mode 100644 sources/AppBundle/Controller/Admin/Accounting/Produit/ListProduitAction.php create mode 100644 templates/admin/accounting/produit/form.html.twig create mode 100644 templates/admin/accounting/produit/list.html.twig create mode 100644 tests/behat/features/Admin/Tresorerie/Produits.feature diff --git a/app/config/packages/backoffice_menu.yaml b/app/config/packages/backoffice_menu.yaml index 73abfde72..aa2101a13 100644 --- a/app/config/packages/backoffice_menu.yaml +++ b/app/config/packages/backoffice_menu.yaml @@ -260,6 +260,14 @@ parameters: - admin_accounting_payments_list - admin_accounting_accounts_list - admin_accounting_rules_list + compta_produits: + nom: 'Produits' + niveau: 'ROLE_ADMIN' + url: '/admin/accounting/produits' + extra_routes: + - admin_accounting_produits_list + - admin_accounting_produits_add + - admin_accounting_produits_edit compta_recherche: nom: 'Recherche comptable' niveau: 'ROLE_ADMIN' diff --git a/app/config/routing/admin_accounting.yml b/app/config/routing/admin_accounting.yml index c4d615ea4..166746cfa 100644 --- a/app/config/routing/admin_accounting.yml +++ b/app/config/routing/admin_accounting.yml @@ -10,6 +10,10 @@ admin_accounting_journal: resource: "admin_accounting/journal.yml" prefix: /journal +admin_accounting_produits: + resource: "admin_accounting/produits.yml" + prefix: /produits + admin_accounting_quotations_list: path: /quotations/list defaults: {_controller: AppBundle\Controller\Admin\Accounting\Quotation\ListQuotationAction} diff --git a/app/config/routing/admin_accounting/produits.yml b/app/config/routing/admin_accounting/produits.yml new file mode 100644 index 000000000..e7a8fc425 --- /dev/null +++ b/app/config/routing/admin_accounting/produits.yml @@ -0,0 +1,19 @@ +admin_accounting_produits_list: + path: / + defaults: {_controller: AppBundle\Controller\Admin\Accounting\Produit\ListProduitAction} + +admin_accounting_produits_add: + path: /add + defaults: {_controller: AppBundle\Controller\Admin\Accounting\Produit\AddProduitAction} + +admin_accounting_produits_edit: + path: /edit/{id} + defaults: {_controller: AppBundle\Controller\Admin\Accounting\Produit\EditProduitAction} + requirements: + id: '\d+' + +admin_accounting_produits_delete: + path: /delete/{id} + defaults: {_controller: AppBundle\Controller\Admin\Accounting\Produit\DeleteProduitAction} + requirements: + id: '\d+' diff --git a/db/migrations/20260508153741_create_compta_produit_table.php b/db/migrations/20260508153741_create_compta_produit_table.php new file mode 100644 index 000000000..074af8a1c --- /dev/null +++ b/db/migrations/20260508153741_create_compta_produit_table.php @@ -0,0 +1,19 @@ +table('compta_produit') + ->addColumn('reference', 'string', ['limit' => 255, 'null' => false]) + ->addColumn('designation', 'string', ['limit' => 255, 'null' => false]) + ->addColumn('quantite', 'integer', ['null' => true]) + ->addColumn('prix_unitaire_ht', 'float', ['null' => false]) + ->addColumn('taux_tva', 'float', ['null' => true]) + ->create(); + } +} diff --git a/sources/AppBundle/Accounting/Entity/Produit.php b/sources/AppBundle/Accounting/Entity/Produit.php new file mode 100644 index 000000000..71be5cb23 --- /dev/null +++ b/sources/AppBundle/Accounting/Entity/Produit.php @@ -0,0 +1,32 @@ + + */ +final class ProduitRepository extends EntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Produit::class); + } + + /** + * @return array + */ + public function getAllSortedByReference(): array + { + return $this->createQueryBuilder('p') + ->orderBy('p.reference', 'asc') + ->getQuery() + ->execute(); + } +} diff --git a/sources/AppBundle/Accounting/Form/ProduitType.php b/sources/AppBundle/Accounting/Form/ProduitType.php new file mode 100644 index 000000000..e3661eb3b --- /dev/null +++ b/sources/AppBundle/Accounting/Form/ProduitType.php @@ -0,0 +1,63 @@ +add('reference', TextType::class, [ + 'label' => 'Référence', + 'required' => true, + 'constraints' => [ + new Assert\NotBlank(), + new Assert\Type('string'), + new Assert\Length(max: 20), + ], + ])->add('designation', TextareaType::class, [ + 'label' => 'Désignation', + 'required' => true, + 'constraints' => [ + new Assert\NotBlank(), + new Assert\Type('string'), + new Assert\Length(max: 100), + ], + ])->add('quantite', IntegerType::class, [ + 'label' => 'Quantité par défaut', + 'required' => false, + 'constraints' => [ + new Assert\Positive(), + ], + ])->add('prixUnitaireHt', NumberType::class, [ + 'label' => 'Prix unitaire HT', + 'required' => true, + 'scale' => 2, + 'constraints' => [ + new Assert\NotBlank(), + new Assert\Positive(), + ], + ])->add('tauxTva', ChoiceType::class, [ + 'label' => 'Taux de TVA', + 'required' => false, + 'expanded' => true, + 'multiple' => false, + 'placeholder' => 'Non soumis', + 'choices' => [ + '5.5%' => 5.5, + '10%' => 10.0, + '20%' => 20.0, + ], + ]); + } +} diff --git a/sources/AppBundle/Controller/Admin/Accounting/Produit/AddProduitAction.php b/sources/AppBundle/Controller/Admin/Accounting/Produit/AddProduitAction.php new file mode 100644 index 000000000..22378f5d6 --- /dev/null +++ b/sources/AppBundle/Controller/Admin/Accounting/Produit/AddProduitAction.php @@ -0,0 +1,41 @@ +createForm(ProduitType::class, $produit); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $this->produitRepository->save($produit); + $this->audit->log('Ajout du produit ' . $produit->reference); + $this->addFlash('notice', 'Le produit a été ajouté'); + return $this->redirectToRoute('admin_accounting_produits_list'); + } + + return $this->render('admin/accounting/produit/form.html.twig', [ + 'form' => $form->createView(), + 'produit' => $produit, + 'formTitle' => 'Ajouter un produit', + 'submitLabel' => 'Ajouter', + ]); + } +} diff --git a/sources/AppBundle/Controller/Admin/Accounting/Produit/DeleteProduitAction.php b/sources/AppBundle/Controller/Admin/Accounting/Produit/DeleteProduitAction.php new file mode 100644 index 000000000..d9b0e03ee --- /dev/null +++ b/sources/AppBundle/Controller/Admin/Accounting/Produit/DeleteProduitAction.php @@ -0,0 +1,34 @@ +produitRepository->find($id); + if (!$produit instanceof Produit) { + $this->addFlash('error', 'Une erreur est survenue lors de la suppression du produit'); + return $this->redirectToRoute('admin_accounting_produits_list'); + } + + $this->produitRepository->delete($produit); + $this->audit->log('Suppression du produit ' . $produit->reference); + $this->addFlash('notice', 'Le produit a été supprimé'); + return $this->redirectToRoute('admin_accounting_produits_list'); + } +} diff --git a/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php b/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php new file mode 100644 index 000000000..9e03952c7 --- /dev/null +++ b/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php @@ -0,0 +1,40 @@ +produitRepository->find($id); + $form = $this->createForm(ProduitType::class, $produit); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $this->produitRepository->save($produit); + $this->audit->log('Modification du produit ' . $produit->reference); + $this->addFlash('notice', 'Le produit a été modifié'); + return $this->redirectToRoute('admin_accounting_produits_list'); + } + + return $this->render('admin/accounting/produit/form.html.twig', [ + 'form' => $form->createView(), + 'produit' => $produit, + 'formTitle' => 'Modifier un produit', + 'submitLabel' => 'Modifier', + ]); + } +} diff --git a/sources/AppBundle/Controller/Admin/Accounting/Produit/ListProduitAction.php b/sources/AppBundle/Controller/Admin/Accounting/Produit/ListProduitAction.php new file mode 100644 index 000000000..451ab623d --- /dev/null +++ b/sources/AppBundle/Controller/Admin/Accounting/Produit/ListProduitAction.php @@ -0,0 +1,27 @@ +produitRepository->getAllSortedByReference(); + + return new Response($this->twig->render('admin/accounting/produit/list.html.twig', [ + 'produits' => $produits, + ])); + } +} diff --git a/sources/AppBundle/Controller/Admin/Accounting/Quotation/AddQuotationAction.php b/sources/AppBundle/Controller/Admin/Accounting/Quotation/AddQuotationAction.php index bfe8f8e0b..bb927191e 100644 --- a/sources/AppBundle/Controller/Admin/Accounting/Quotation/AddQuotationAction.php +++ b/sources/AppBundle/Controller/Admin/Accounting/Quotation/AddQuotationAction.php @@ -5,6 +5,7 @@ namespace AppBundle\Controller\Admin\Accounting\Quotation; use Afup\Site\Comptabilite\Facture; +use AppBundle\Accounting\Entity\Repository\ProduitRepository; use AppBundle\Accounting\Form\QuotationType; use AppBundle\Accounting\Model\Invoicing; use AppBundle\Accounting\Model\InvoicingDetail; @@ -20,6 +21,7 @@ public function __construct( private readonly InvoicingRepository $invoicingRepository, private readonly Facture $facture, private readonly InvoicingDetailRepository $invoicingDetailRepository, + private readonly ProduitRepository $produitRepository, ) {} public function __invoke(Request $request): Response @@ -49,6 +51,7 @@ public function __invoke(Request $request): Response 'quotation' => $quotation, 'form' => $form->createView(), 'submitLabel' => 'Ajouter', + 'produits' => $this->produitRepository->getAllSortedByReference(), ]); } @@ -59,7 +62,6 @@ private function init(int $quotationId): Invoicing $quotation = new Invoicing(); $quotation->setQuotationDate(new \DateTime()); $quotation->setCountryId('FR'); - $quotation->setDetails([new InvoicingDetail()]); return $quotation; } diff --git a/sources/AppBundle/Controller/Admin/Accounting/Quotation/EditQuotationAction.php b/sources/AppBundle/Controller/Admin/Accounting/Quotation/EditQuotationAction.php index 01506d042..4b14da1f6 100644 --- a/sources/AppBundle/Controller/Admin/Accounting/Quotation/EditQuotationAction.php +++ b/sources/AppBundle/Controller/Admin/Accounting/Quotation/EditQuotationAction.php @@ -4,6 +4,7 @@ namespace AppBundle\Controller\Admin\Accounting\Quotation; +use AppBundle\Accounting\Entity\Repository\ProduitRepository; use AppBundle\Accounting\Form\QuotationType; use AppBundle\Accounting\Model\Repository\InvoicingDetailRepository; use AppBundle\Accounting\Model\Repository\InvoicingRepository; @@ -16,6 +17,7 @@ class EditQuotationAction extends AbstractController public function __construct( private readonly InvoicingRepository $invoicingRepository, private readonly InvoicingDetailRepository $invoicingDetailRepository, + private readonly ProduitRepository $produitRepository, ) {} public function __invoke(Request $request): Response @@ -59,6 +61,7 @@ public function __invoke(Request $request): Response 'quotation' => $quotation, 'form' => $form->createView(), 'submitLabel' => 'Modifier', + 'produits' => $this->produitRepository->getAllSortedByReference(), ]); } } diff --git a/templates/admin/accounting/produit/form.html.twig b/templates/admin/accounting/produit/form.html.twig new file mode 100644 index 000000000..d70bcf82b --- /dev/null +++ b/templates/admin/accounting/produit/form.html.twig @@ -0,0 +1,45 @@ +{% extends 'admin/base_with_header.html.twig' %} + +{% form_theme form 'form_theme_admin.html.twig' %} + +{% block content %} +

{{ formTitle }}

+ + {{ form_start(form) }} + +
+

Produit

+
+
+
+ {{ form_row(form.reference) }} + {{ form_row(form.designation) }} + {{ form_row(form.quantite) }} + {{ form_row(form.prixUnitaireHt) }} + {{ form_row(form.tauxTva) }} +
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+

+ * indique un champ obligatoire +

+
+ + {{ form_end(form) }} + +{% endblock %} diff --git a/templates/admin/accounting/produit/list.html.twig b/templates/admin/accounting/produit/list.html.twig new file mode 100644 index 000000000..01e086500 --- /dev/null +++ b/templates/admin/accounting/produit/list.html.twig @@ -0,0 +1,64 @@ +{% extends 'admin/base_with_header.html.twig' %} + +{% block content %} + +

Produits

+ + + + + + + + + + + + + + + + {% for produit in produits %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
RéférenceDésignationQuantité par défautPrix unitaire HTTaux de TVA
{{ produit.reference }}{{ produit.designation }}{{ produit.quantite ?? '-' }}{{ produit.prixUnitaireHt|number_format(2, ',', ' ') }} € + {%- if produit.tauxTva is not null -%} + {{ produit.tauxTva }} % + {%- else -%} + non soumis + {%- endif -%} + + + + + + + +
Aucun produit actuellement
+ +{% endblock %} diff --git a/templates/admin/accounting/quotation/_javascript.html.twig b/templates/admin/accounting/quotation/_javascript.html.twig index 250c52679..2ed670363 100644 --- a/templates/admin/accounting/quotation/_javascript.html.twig +++ b/templates/admin/accounting/quotation/_javascript.html.twig @@ -1,7 +1,10 @@ diff --git a/templates/admin/accounting/quotation/form.html.twig b/templates/admin/accounting/quotation/form.html.twig index 15513dc6b..523564a20 100644 --- a/templates/admin/accounting/quotation/form.html.twig +++ b/templates/admin/accounting/quotation/form.html.twig @@ -117,7 +117,7 @@
-

Contenu

+

Contenu

{% set index = form.details|length > 0 ? (form.details|length) : 0 %} @@ -146,7 +146,11 @@
- Ajouter une ligne + Ajouter une ligne vide + {% if produits|length > 0 %} + + Ajouter une ligne depuis un produit + {% endif %}
@@ -170,3 +174,52 @@ {{ form_end(form) }} +{% if produits|length > 0 %} + +{% endif %} diff --git a/tests/behat/features/Admin/Tresorerie/DevisFactures.feature b/tests/behat/features/Admin/Tresorerie/DevisFactures.feature index f22785187..972ad3d78 100644 --- a/tests/behat/features/Admin/Tresorerie/DevisFactures.feature +++ b/tests/behat/features/Admin/Tresorerie/DevisFactures.feature @@ -16,6 +16,7 @@ Feature: Administration - Trésorerie - Devis/Facture And I fill in "quotation[city]" with "Dijon" And I fill in "quotation[zipcode]" with "21000" And I fill in "quotation[email]" with "martine@ens-corp.biz" + And I click on link with class "add_item_link" And I fill in "quotation[details][0][reference]" with "COACH-001" And I fill in "quotation[details][0][designation]" with "Coaching d'équipe pour l'accompagnement sans douleur à Symfony" And I fill in "quotation[details][0][quantity]" with "12" @@ -43,6 +44,7 @@ Feature: Administration - Trésorerie - Devis/Facture And I fill in "quotation[refClt2]" with "AFGD5S" And I fill in "quotation[refClt3]" with "000AFGD5S" And I fill in "quotation[observation]" with "Ce devis ne comprend pas les selfies avec l'équipe" + And I click on link with class "add_item_link" And I fill in "quotation[details][0][reference]" with "COACH-001" And I fill in "quotation[details][0][designation]" with "Coaching d'équipe pour l'accompagnement sans douleur à Symfony" And I fill in "quotation[details][0][quantity]" with "12" diff --git a/tests/behat/features/Admin/Tresorerie/Produits.feature b/tests/behat/features/Admin/Tresorerie/Produits.feature new file mode 100644 index 000000000..783c9d006 --- /dev/null +++ b/tests/behat/features/Admin/Tresorerie/Produits.feature @@ -0,0 +1,37 @@ +Feature: Administration - Trésorerie - Produits + + @reloadDbWithTestData + Scenario: Création / édition / suppression d'un produit + Given I am logged in as admin and on the Administration + When I follow "Produits" + Then the ".content h2" element should contain "Produits" + When I follow "Ajouter" + Then the ".content h2" element should contain "Ajouter un produit" + And I fill in "produit[reference]" with "forum_php_2026" + And I fill in "produit[designation]" with "Forum PHP 2026 - Sponsoring Argent sans stand" + And I fill in "produit[quantite]" with "1" + And I fill in "produit[prixUnitaireHt]" with "123,45" + And I select "20" from "produit[tauxTva]" + And I press "Ajouter" + Then the ".content .message" element should contain "Le produit a été ajouté" + And I should see "Forum PHP 2026 - Sponsoring Argent sans stand" + And I should see "forum_php_2026" + + When I follow the button of tooltip "Modifier la ligne" + Then the ".content h2" element should contain "Modifier un produit" + And I fill in "produit[reference]" with "forum_php_2027" + And I fill in "produit[designation]" with "Forum PHP 2027 - Sponsoring Argent sans stand" + And I fill in "produit[quantite]" with "2" + And I fill in "produit[prixUnitaireHt]" with "456" + And I select "5.5" from "produit[tauxTva]" + And I press "Modifier" + Then the ".content .message" element should contain "Le produit a été modifié" + And I should see "forum_php_2027" + And I should see "Forum PHP 2027 - Sponsoring Argent sans stand" + And I should see "2" + And I should see "456" + And I should see "5.5 %" + + When I follow the button of tooltip "Supprimer la ligne" + Then the ".content .message" element should contain "Le produit a été supprimé" + And I should not see "forum_php_2027" From 48da03872ff2d6c1538885913e0feb9a0df13e02 Mon Sep 17 00:00:00 2001 From: Nathan Boiron Date: Sun, 10 May 2026 21:20:11 +0200 Subject: [PATCH 2/2] Retours PR --- sources/AppBundle/Accounting/Entity/Produit.php | 2 +- .../Admin/Accounting/Produit/EditProduitAction.php | 6 ++++++ templates/admin/accounting/produit/list.html.twig | 4 ++-- templates/admin/accounting/quotation/_javascript.html.twig | 1 + templates/admin/accounting/quotation/form.html.twig | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/sources/AppBundle/Accounting/Entity/Produit.php b/sources/AppBundle/Accounting/Entity/Produit.php index 71be5cb23..b6b25bce6 100644 --- a/sources/AppBundle/Accounting/Entity/Produit.php +++ b/sources/AppBundle/Accounting/Entity/Produit.php @@ -24,7 +24,7 @@ class Produit #[ORM\Column(nullable: true)] public ?int $quantite = null; - #[ORM\Column(nullable: false)] + #[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: false)] public float $prixUnitaireHt; #[ORM\Column(nullable: true)] diff --git a/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php b/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php index 9e03952c7..3e512667e 100644 --- a/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php +++ b/sources/AppBundle/Controller/Admin/Accounting/Produit/EditProduitAction.php @@ -4,6 +4,7 @@ namespace AppBundle\Controller\Admin\Accounting\Produit; +use AppBundle\Accounting\Entity\Produit; use AppBundle\Accounting\Entity\Repository\ProduitRepository; use AppBundle\Accounting\Form\ProduitType; use AppBundle\AuditLog\Audit; @@ -21,8 +22,13 @@ public function __construct( public function __invoke(int $id, Request $request): Response { $produit = $this->produitRepository->find($id); + if (!$produit instanceof Produit) { + throw $this->createNotFoundException(sprintf('Le produit n\'a pas été trouvé avec l\'id "%s"', $id)); + } + $form = $this->createForm(ProduitType::class, $produit); $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { $this->produitRepository->save($produit); $this->audit->log('Modification du produit ' . $produit->reference); diff --git a/templates/admin/accounting/produit/list.html.twig b/templates/admin/accounting/produit/list.html.twig index 01e086500..2030c7404 100644 --- a/templates/admin/accounting/produit/list.html.twig +++ b/templates/admin/accounting/produit/list.html.twig @@ -41,7 +41,7 @@ data-tooltip="Modifier la ligne" class="compact ui icon button" > - + {% else %} - Aucun produit actuellement + Aucun produit actuellement {% endfor %} diff --git a/templates/admin/accounting/quotation/_javascript.html.twig b/templates/admin/accounting/quotation/_javascript.html.twig index 2ed670363..58697ff87 100644 --- a/templates/admin/accounting/quotation/_javascript.html.twig +++ b/templates/admin/accounting/quotation/_javascript.html.twig @@ -69,6 +69,7 @@ newRow.querySelector('[name$="[quantity]"]').value = btn.dataset.quantite || ''; newRow.querySelector('[name$="[unitPrice]"]').value = btn.dataset.prixUnitaireHt; newRow.querySelector('[name$="[tva]"]').value = btn.dataset.tauxTva; + newRow.querySelector('[name$="[designation]"]').value = btn.dataset.designation; $modal.modal('hide'); }); diff --git a/templates/admin/accounting/quotation/form.html.twig b/templates/admin/accounting/quotation/form.html.twig index 523564a20..74b5ead82 100644 --- a/templates/admin/accounting/quotation/form.html.twig +++ b/templates/admin/accounting/quotation/form.html.twig @@ -209,7 +209,7 @@ data-designation="{{ product.designation|e('html_attr') }}" data-quantite="{{ product.quantite }}" data-prix-unitaire-ht="{{ product.prixUnitaireHt }}" - data-taux-tva="{{ product.tauxTva ?? 0 }}"> + data-taux-tva="{{ product.tauxTva ?? '' }}"> Choisir