feat: enregistrement paiement manuel sur avis de paiement
- Bouton "Enregistrer paiement" sur les avis au state 'send' - Modal avec montant, methode (virement/cheque/especes/CB externe/autre), reference optionnelle - Route manualPayment : cree AdvertPayment, passe avis en accepted, genere facture payee, indexe Meilisearch - Methodes : virement, cheque, especes, CB terminal externe, autre Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -268,6 +268,74 @@ class AdvertController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enregistre un paiement manuel (virement, cheque, especes, etc.).
|
||||||
|
*/
|
||||||
|
#[Route('/{id}/manual-payment', name: 'manual_payment', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||||
|
public function manualPayment(
|
||||||
|
int $id,
|
||||||
|
\Symfony\Component\HttpFoundation\Request $request,
|
||||||
|
FactureService $factureService,
|
||||||
|
): Response {
|
||||||
|
$advert = $this->em->getRepository(Advert::class)->find($id);
|
||||||
|
if (null === $advert) {
|
||||||
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = $request->request->getString('amount');
|
||||||
|
$method = $request->request->getString('method');
|
||||||
|
$reference = trim($request->request->getString('reference'));
|
||||||
|
|
||||||
|
if ('' === $amount || '' === $method) {
|
||||||
|
$this->addFlash('error', 'Montant et methode de paiement requis.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_admin_clients_show', [
|
||||||
|
'id' => $advert->getCustomer()?->getId() ?? 0,
|
||||||
|
'tab' => 'avis',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodLabel = match ($method) {
|
||||||
|
'virement' => 'Virement bancaire',
|
||||||
|
'cheque' => 'Cheque',
|
||||||
|
'especes' => 'Especes',
|
||||||
|
'cb_externe' => 'CB (terminal externe)',
|
||||||
|
default => 'Autre',
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('' !== $reference) {
|
||||||
|
$methodLabel .= ' (Ref: '.$reference.')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creer l'AdvertPayment
|
||||||
|
$payment = new \App\Entity\AdvertPayment($advert, \App\Entity\AdvertPayment::TYPE_SUCCESS, number_format((float) $amount, 2, '.', ''));
|
||||||
|
$payment->setMethod($method);
|
||||||
|
$this->em->persist($payment);
|
||||||
|
|
||||||
|
// Mettre a jour l'etat de l'avis
|
||||||
|
$advert->setState(Advert::STATE_ACCEPTED);
|
||||||
|
|
||||||
|
// Tracker l'evenement
|
||||||
|
$this->em->persist(new \App\Entity\AdvertEvent($advert, \App\Entity\AdvertEvent::TYPE_PAY, 'Paiement manuel : '.$methodLabel.' - '.$amount.' EUR'));
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
// Generer la facture
|
||||||
|
try {
|
||||||
|
$factureService->createPaidFactureFromAdvert($advert, number_format((float) $amount, 2, '.', ''), $methodLabel);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->addFlash('warning', 'Paiement enregistre mais erreur generation facture : '.$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->meilisearch->indexAdvert($advert);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Paiement de '.$amount.' EUR enregistre ('.$methodLabel.') pour l\'avis '.$advert->getOrderNumber()->getNumOrder().'.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_admin_clients_show', [
|
||||||
|
'id' => $advert->getCustomer()?->getId() ?? 0,
|
||||||
|
'tab' => 'avis',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/{id}/sync-payment', name: 'sync_payment', requirements: ['id' => '\d+'], methods: ['POST'])]
|
#[Route('/{id}/sync-payment', name: 'sync_payment', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||||
public function syncPayment(
|
public function syncPayment(
|
||||||
int $id,
|
int $id,
|
||||||
|
|||||||
@@ -641,6 +641,10 @@
|
|||||||
<form method="post" action="{{ path('app_admin_advert_resend', {id: a.id}) }}" class="inline" data-confirm="Renvoyer l'avis de paiement au client ?">
|
<form method="post" action="{{ path('app_admin_advert_resend', {id: a.id}) }}" class="inline" data-confirm="Renvoyer l'avis de paiement au client ?">
|
||||||
<button type="submit" class="px-3 py-1 bg-purple-500/20 text-purple-700 hover:bg-purple-500 hover:text-white font-bold uppercase text-[10px]transition-all">Renvoyer</button>
|
<button type="submit" class="px-3 py-1 bg-purple-500/20 text-purple-700 hover:bg-purple-500 hover:text-white font-bold uppercase text-[10px]transition-all">Renvoyer</button>
|
||||||
</form>
|
</form>
|
||||||
|
<button type="button" data-modal-open="modal-manual-pay-{{ a.id }}"
|
||||||
|
class="px-3 py-1 bg-green-500/20 text-green-700 hover:bg-green-500 hover:text-white font-bold uppercase text-[10px] transition-all">
|
||||||
|
Enregistrer paiement
|
||||||
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if a.state == 'accepted' and a.factures|length == 0 %}
|
{% if a.state == 'accepted' and a.factures|length == 0 %}
|
||||||
<form method="post" action="{{ path('app_admin_advert_create_facture', {id: a.id}) }}" class="inline" data-confirm="Creer la facture pour l'avis {{ a.orderNumber.numOrder }} ?">
|
<form method="post" action="{{ path('app_admin_advert_create_facture', {id: a.id}) }}" class="inline" data-confirm="Creer la facture pour l'avis {{ a.orderNumber.numOrder }} ?">
|
||||||
@@ -682,6 +686,44 @@
|
|||||||
<div class="glass p-8 text-center text-gray-400 font-bold">Aucun avis de paiement.</div>
|
<div class="glass p-8 text-center text-gray-400 font-bold">Aucun avis de paiement.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Modals paiement manuel #}
|
||||||
|
{% for a in advertsList %}
|
||||||
|
{% if a.state == 'send' %}
|
||||||
|
<div id="modal-manual-pay-{{ a.id }}" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
|
<div class="glass-heavy p-6 w-full max-w-md">
|
||||||
|
<h2 class="text-lg font-bold uppercase mb-4">Enregistrer un paiement</h2>
|
||||||
|
<p class="text-xs text-gray-500 mb-4">Avis {{ a.orderNumber.numOrder }} - {{ a.totalTtc }} €</p>
|
||||||
|
<form method="post" action="{{ path('app_admin_advert_manual_payment', {id: a.id}) }}">
|
||||||
|
<div class="grid grid-cols-1 gap-3 mb-4">
|
||||||
|
<div>
|
||||||
|
<label for="mp-amount-{{ a.id }}" class="block text-[9px] font-bold uppercase tracking-wider text-gray-400 mb-1">Montant recu</label>
|
||||||
|
<input type="number" id="mp-amount-{{ a.id }}" name="amount" step="0.01" min="0.01" value="{{ a.totalTtc }}" required class="input-glass w-full px-3 py-2 text-xs font-bold">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="mp-method-{{ a.id }}" class="block text-[9px] font-bold uppercase tracking-wider text-gray-400 mb-1">Methode de paiement</label>
|
||||||
|
<select id="mp-method-{{ a.id }}" name="method" required class="input-glass w-full px-3 py-2 text-xs font-bold">
|
||||||
|
<option value="virement">Virement bancaire</option>
|
||||||
|
<option value="cheque">Cheque</option>
|
||||||
|
<option value="especes">Especes</option>
|
||||||
|
<option value="cb_externe">CB (terminal externe)</option>
|
||||||
|
<option value="autre">Autre</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="mp-reference-{{ a.id }}" class="block text-[9px] font-bold uppercase tracking-wider text-gray-400 mb-1">Reference (optionnel)</label>
|
||||||
|
<input type="text" id="mp-reference-{{ a.id }}" name="reference" placeholder="N de virement, n de cheque..." class="input-glass w-full px-3 py-2 text-xs font-bold">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button type="button" data-modal-close="modal-manual-pay-{{ a.id }}" class="px-4 py-2 glass font-bold uppercase text-[10px] tracking-widest">Annuler</button>
|
||||||
|
<button type="submit" class="px-4 py-2 bg-green-600 text-white font-bold uppercase text-[10px] tracking-wider hover:bg-green-700 transition-all">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{# Tab: Devis #}
|
{# Tab: Devis #}
|
||||||
{% elseif tab == 'devis' %}
|
{% elseif tab == 'devis' %}
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user