feat: page admin de gestion de la numerotation des commandes
src/Controller/Admin/OrderNumberController.php (nouveau): - Route /admin/numerotation, accessible ROLE_ROOT uniquement - index(): affiche le prochain numero via OrderNumberService::preview() et les 20 derniers numeros generes (ORDER BY id DESC) - update(): modifie le prochain numero en creant une entree placeholder avec le numero precedent (N-1) marque comme utilise, pour que le prochain generate() retourne le numero souhaite - Validation du format MM/YYYY-XXXXX via regex - Verification que le numero n'existe pas deja - Verification que le numero est au minimum 00001 templates/admin/order_number/index.html.twig (nouveau): - Section "Prochain numero" : affiche le prochain numero en gros (gold) avec formulaire pour le modifier (input avec pattern regex, placeholder MM/YYYY-XXXXX, explication de l'utilite) - Section "Derniers numeros generes" : tableau avec numero (font-mono), date de creation, statut (Utilise vert / Reserve gris) - Design glassmorphism (glass, input-glass, btn-gold, glass-dark header) templates/admin/_layout.html.twig: - Ajout du lien "Numerotation" dans la sidebar Super Admin avec icone hash (#), route app_admin_order_number, style active-danger Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
src/Controller/Admin/OrderNumberController.php
Normal file
83
src/Controller/Admin/OrderNumberController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\OrderNumber;
|
||||
use App\Repository\OrderNumberRepository;
|
||||
use App\Service\OrderNumberService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[Route('/admin/numerotation', name: 'app_admin_order_number')]
|
||||
#[IsGranted('ROLE_ROOT')]
|
||||
class OrderNumberController extends AbstractController
|
||||
{
|
||||
#[Route('', name: '')]
|
||||
public function index(OrderNumberService $orderNumberService, OrderNumberRepository $repository): Response
|
||||
{
|
||||
$nextNumber = $orderNumberService->preview();
|
||||
|
||||
$lastOrders = $repository->createQueryBuilder('o')
|
||||
->orderBy('o.id', 'DESC')
|
||||
->setMaxResults(20)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return $this->render('admin/order_number/index.html.twig', [
|
||||
'nextNumber' => $nextNumber,
|
||||
'lastOrders' => $lastOrders,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/update', name: '_update', methods: ['POST'])]
|
||||
public function update(Request $request, OrderNumberRepository $repository, EntityManagerInterface $em): Response
|
||||
{
|
||||
$newNumber = trim($request->request->getString('next_number'));
|
||||
|
||||
if ('' === $newNumber || !preg_match('/^\d{2}\/\d{4}-\d{5}$/', $newNumber)) {
|
||||
$this->addFlash('error', 'Format invalide. Utilisez le format MM/YYYY-XXXXX (ex: 04/2026-00042).');
|
||||
|
||||
return $this->redirectToRoute('app_admin_order_number');
|
||||
}
|
||||
|
||||
// Verifier si ce numero existe deja
|
||||
$existing = $repository->findOneBy(['numOrder' => $newNumber]);
|
||||
if (null !== $existing) {
|
||||
$this->addFlash('error', 'Le numero '.$newNumber.' existe deja.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_order_number');
|
||||
}
|
||||
|
||||
// Extraire le prefix et le compteur
|
||||
$parts = explode('-', $newNumber);
|
||||
$prefix = $parts[0].'-';
|
||||
$targetNum = (int) $parts[1];
|
||||
|
||||
// On cree l'entree precedente pour que le prochain generate() retourne le bon numero
|
||||
$previousNum = $targetNum - 1;
|
||||
if ($previousNum < 0) {
|
||||
$this->addFlash('error', 'Le numero doit etre au minimum 00001.');
|
||||
|
||||
return $this->redirectToRoute('app_admin_order_number');
|
||||
}
|
||||
|
||||
$previousNumOrder = $prefix.str_pad((string) $previousNum, 5, '0', \STR_PAD_LEFT);
|
||||
|
||||
// Verifier si l'entree precedente existe deja
|
||||
$existingPrevious = $repository->findOneBy(['numOrder' => $previousNumOrder]);
|
||||
if (null === $existingPrevious && $previousNum > 0) {
|
||||
$placeholder = new OrderNumber($previousNumOrder);
|
||||
$placeholder->markAsUsed();
|
||||
$em->persist($placeholder);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
$this->addFlash('success', 'Prochain numero de commande mis a jour : '.$newNumber);
|
||||
|
||||
return $this->redirectToRoute('app_admin_order_number');
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,10 @@
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||
Synchronisation
|
||||
</a>
|
||||
<a href="{{ path('app_admin_order_number') }}" class="sidebar-nav-item {{ current_route starts with 'app_admin_order_number' ? 'active-danger' : '' }}" style="color: {{ current_route starts with 'app_admin_order_number' ? 'white' : 'rgba(248,113,113,0.7)' }}">
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/></svg>
|
||||
Numerotation
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
86
templates/admin/order_number/index.html.twig
Normal file
86
templates/admin/order_number/index.html.twig
Normal file
@@ -0,0 +1,86 @@
|
||||
{% extends 'admin/_layout.html.twig' %}
|
||||
|
||||
{% block title %}Numerotation - Administration - CRM SITECONSEIL{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
<div class="page-container">
|
||||
<h1 class="text-2xl font-bold heading-page mb-8">Numerotation des commandes</h1>
|
||||
|
||||
{% for type, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
<div class="mb-6 p-4 glass font-medium text-sm rounded-xl" style="border-color: {{ type == 'success' ? 'rgba(34,197,94,0.3)' : 'rgba(220,38,38,0.3)' }}; color: {{ type == 'success' ? '#166534' : '#991b1b' }};">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold uppercase mb-4">Prochain numero</h2>
|
||||
<div class="glass p-6">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<div>
|
||||
<p class="text-xs font-bold uppercase tracking-wider text-gray-500 mb-1">Prochain numero genere</p>
|
||||
<p class="text-3xl font-bold text-[#fabf04]">{{ nextNumber }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-white/20 pt-6">
|
||||
<h3 class="text-sm font-bold uppercase tracking-wider mb-3">Modifier le prochain numero</h3>
|
||||
<p class="text-xs text-gray-500 mb-4">Utilisez cette fonction si des factures ont ete creees en dehors du CRM et que vous devez ajuster la numerotation pour eviter les doublons.</p>
|
||||
<form method="post" action="{{ path('app_admin_order_number_update') }}" class="flex flex-col sm:flex-row items-start sm:items-end gap-3">
|
||||
<div class="flex-1 w-full sm:w-auto">
|
||||
<label for="next_number" class="block text-xs font-bold uppercase tracking-wider mb-2 text-gray-600">Prochain numero souhaite</label>
|
||||
<input type="text" id="next_number" name="next_number" required
|
||||
value="{{ nextNumber }}"
|
||||
pattern="\d{2}/\d{4}-\d{5}"
|
||||
placeholder="MM/YYYY-XXXXX"
|
||||
class="input-glass w-full sm:w-64 px-4 py-3 text-sm font-medium font-mono">
|
||||
</div>
|
||||
<button type="submit" class="btn-gold px-6 py-3 text-sm font-bold uppercase tracking-wider text-gray-900">
|
||||
Mettre a jour
|
||||
</button>
|
||||
</form>
|
||||
<p class="text-[10px] text-gray-400 mt-2">Format : MM/YYYY-XXXXX (ex: 04/2026-00042)</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold uppercase mb-4">Derniers numeros generes</h2>
|
||||
<div class="glass overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="glass-dark" style="border-radius: 0;">
|
||||
<th class="px-4 py-3 text-left font-bold uppercase text-xs tracking-wider text-white/80">Numero</th>
|
||||
<th class="px-4 py-3 text-left font-bold uppercase text-xs tracking-wider text-white/80">Date de creation</th>
|
||||
<th class="px-4 py-3 text-center font-bold uppercase text-xs tracking-wider text-white/80">Statut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in lastOrders %}
|
||||
<tr class="border-b border-white/10 hover:bg-white/30 transition-colors">
|
||||
<td class="px-4 py-3 font-mono font-bold">{{ order.numOrder }}</td>
|
||||
<td class="px-4 py-3 text-gray-500">{{ order.createdAt|date('d/m/Y H:i:s') }}</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
{% if order.used %}
|
||||
<span class="px-2 py-0.5 bg-green-500/20 text-green-700 font-bold uppercase text-[10px] rounded">Utilise</span>
|
||||
{% else %}
|
||||
<span class="px-2 py-0.5 bg-gray-500/20 text-gray-600 font-bold uppercase text-[10px] rounded">Reserve</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="px-4 py-8 text-center text-gray-400 font-bold">Aucun numero genere.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user