feat: modification et suppression des paiements avec validation dynamique
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m7s
AS Talange CI/CD Pipeline / Build & Run in Docker Container (push) Successful in 2m53s

This commit is contained in:
2026-05-31 02:44:00 +02:00
parent 52fbd5fcf8
commit e39921f049
3 changed files with 220 additions and 11 deletions
@@ -57,4 +57,56 @@ public class PaiementController {
return "redirect:/adherents/" + adherentId + "/edit";
}
@PostMapping("/paiements/{id}/update")
public String updatePaiement(
@PathVariable Long id,
@RequestParam BigDecimal montant,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate datePaiement,
@RequestParam Long modePaiementId,
@RequestParam(required = false) String redirect) {
Paiement paiement = paiementRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid Paiement ID"));
ModePaiement mode = modePaiementRepository.findById(modePaiementId)
.orElseThrow(() -> new IllegalArgumentException("Invalid ModePaiement ID"));
Licence licence = paiement.getLicence();
// Validation basique pour ne pas dépasser le montant total de la licence
BigDecimal maxAmount = licence.getResteAPayer().add(paiement.getMontant());
if (montant.compareTo(maxAmount) > 0) {
montant = maxAmount;
}
if (montant.compareTo(BigDecimal.ZERO) > 0) {
paiement.setModePaiement(mode);
paiement.setMontant(montant);
paiement.setDatePaiement(datePaiement);
paiementRepository.save(paiement);
}
if (redirect != null && !redirect.isEmpty()) {
return "redirect:" + redirect;
}
return "redirect:/adherents/" + licence.getAdherent().getId() + "/edit";
}
@PostMapping("/paiements/{id}/delete")
public String deletePaiement(
@PathVariable Long id,
@RequestParam(required = false) String redirect) {
Paiement paiement = paiementRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid Paiement ID"));
Long adherentId = paiement.getLicence().getAdherent().getId();
paiementRepository.delete(paiement);
if (redirect != null && !redirect.isEmpty()) {
return "redirect:" + redirect;
}
return "redirect:/adherents/" + adherentId + "/edit";
}
}
@@ -205,16 +205,54 @@
</td>
</tr>
<tr th:if="${!#lists.isEmpty(lic.paiements)}" class="bg-gray-50/50">
<td colspan="7" class="py-2 px-4">
<div class="text-xs text-gray-500 mb-1 font-medium uppercase tracking-wider">Historique des paiements</div>
<div class="flex flex-wrap gap-2">
<span th:each="paiement : ${lic.paiements}" class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-white border border-gray-200 text-gray-700">
<span th:text="${paiement.montant + ' €'}">50 €</span>
<span class="mx-1 text-gray-300">|</span>
<span th:text="${paiement.modePaiement.nom}">Chèque</span>
<span class="mx-1 text-gray-300">|</span>
<span th:text="${#temporals.format(paiement.datePaiement, 'dd/MM/yyyy')}">01/01/2026</span>
</span>
<td colspan="7" class="py-3 px-6">
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Historique des paiements</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 bg-white rounded-lg border border-gray-200 shadow-sm text-xs">
<thead>
<tr class="bg-gray-50 text-gray-500 uppercase font-medium">
<th class="py-2 px-3 text-left">Date</th>
<th class="py-2 px-3 text-left">Mode</th>
<th class="py-2 px-3 text-right font-medium">Montant</th>
<th class="py-2 px-3 text-right font-medium pr-4">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tr th:each="paiement : ${lic.paiements}" class="hover:bg-gray-50">
<td class="py-2 px-3 text-gray-600 font-medium" th:text="${#temporals.format(paiement.datePaiement, 'dd/MM/yyyy')}">01/01/2026</td>
<td class="py-2 px-3 text-gray-600">
<span class="px-2 py-0.5 rounded bg-gray-100 text-gray-700 font-mono text-[10px]" th:text="${paiement.modePaiement.nom}">Chèque</span>
</td>
<td class="py-2 px-3 text-right font-semibold text-green-600" th:text="${paiement.montant + ' €'}">50.00 €</td>
<td class="py-2 px-3 text-right pr-4">
<div class="flex justify-end items-center space-x-2">
<button type="button"
th:data-paiement-id="${paiement.id}"
th:data-montant="${paiement.montant}"
th:data-date="${paiement.datePaiement}"
th:data-mode-id="${paiement.modePaiement.id}"
th:data-licence-id="${lic.id}"
th:data-max-amount="${lic.getResteAPayer().add(paiement.montant)}"
onclick="openEditPaiementModal(this.getAttribute('data-paiement-id'), this.getAttribute('data-montant'), this.getAttribute('data-date'), this.getAttribute('data-mode-id'), this.getAttribute('data-licence-id'), this.getAttribute('data-max-amount'))"
class="text-blue-600 hover:text-blue-900 bg-blue-50 hover:bg-blue-100 p-1.5 rounded transition-colors inline-flex items-center"
title="Modifier">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
</svg>
</button>
<form th:action="@{/paiements/{id}/delete(id=${paiement.id})}" method="post" class="inline m-0" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer ce paiement ?');">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type="submit" class="text-red-600 hover:text-red-900 bg-red-50 hover:bg-red-100 p-1.5 rounded transition-colors inline-flex items-center" title="Supprimer">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</form>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
@@ -397,6 +435,38 @@
</div>
</div>
<!-- Edit Paiement Modal -->
<div id="editPaiementModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-xl bg-white">
<div class="mt-3 text-center">
<h3 class="text-lg leading-6 font-semibold text-gray-900 mb-4">Modifier le Paiement</h3>
<form id="editPaiementForm" method="post" class="space-y-4 text-left">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<input type="hidden" name="adherentId" th:value="${adherent.id}">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Montant (€)</label>
<input type="number" step="0.01" min="0.01" id="editPaiementMontant" name="montant" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="editPaiementDate" name="datePaiement" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Mode de paiement</label>
<select id="editPaiementMode" name="modePaiementId" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<option value="">Sélectionnez un mode...</option>
<option th:each="mode : ${modesPaiement}" th:value="${mode.id}" th:text="${mode.nom}"></option>
</select>
</div>
<div class="items-center px-4 py-3 flex justify-end space-x-2">
<button type="button" onclick="closeEditPaiementModal()" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">Annuler</button>
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
<script th:inline="javascript">
function openLicenceModal(licenceId = null, categorieId = '', numeroLicence = '', etat = 'Brouillon', equipeId = '') {
document.getElementById('licenceModal').classList.remove('hidden');
@@ -478,6 +548,18 @@
function closePaiementModal() {
document.getElementById('paiementModal').classList.add('hidden');
}
function openEditPaiementModal(paiementId, montant, date, modePaiementId, licenceId, maxAmount) {
document.getElementById('editPaiementModal').classList.remove('hidden');
document.getElementById('editPaiementForm').action = '/paiements/' + paiementId + '/update';
document.getElementById('editPaiementMontant').value = montant;
document.getElementById('editPaiementMontant').max = maxAmount;
document.getElementById('editPaiementDate').value = date;
document.getElementById('editPaiementMode').value = modePaiementId;
}
function closeEditPaiementModal() {
document.getElementById('editPaiementModal').classList.add('hidden');
}
document.addEventListener("DOMContentLoaded", function() {
const dateInput = document.getElementById('dateNaissance');
const repBlock = document.getElementById('representantBlock');
@@ -44,6 +44,7 @@
<th class="py-3 px-6 font-medium text-center">Saison</th>
<th class="py-3 px-6 font-medium text-left">Mode de paiement</th>
<th class="py-3 px-6 font-medium text-right">Montant</th>
<th class="py-3 px-6 font-medium text-right pr-6">Actions</th>
</tr>
<!-- Filter Row -->
<tr class="bg-gray-100 border-b border-gray-200 text-xs">
@@ -69,11 +70,12 @@
<td class="py-2 px-3">
<input type="text" id="filterMontant" name="montant" th:value="${montantFilter}" placeholder="Filtrer Montant..." class="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 focus:outline-none text-right">
</td>
<td class="py-2 px-3"></td>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 text-sm">
<tr th:if="${#lists.isEmpty(paiements)}">
<td colspan="6" class="py-8 text-center text-gray-500">Aucun paiement trouvé.</td>
<td colspan="7" class="py-8 text-center text-gray-500">Aucun paiement trouvé.</td>
</tr>
<tr th:each="p : ${paiements}" class="hover:bg-gray-50 transition-colors">
<td class="py-4 px-6 text-gray-600" th:text="${#temporals.format(p.datePaiement, 'dd/MM/yyyy')}">15/05/2026</td>
@@ -89,6 +91,33 @@
th:text="${p.modePaiement.nom}">Chèque</span>
</td>
<td class="py-4 px-6 text-right font-semibold text-green-600" th:text="${p.montant + ' €'}">50.00 €</td>
<td class="py-4 px-6 text-right pr-6">
<div class="flex justify-end items-center space-x-2">
<button type="button"
th:data-paiement-id="${p.id}"
th:data-montant="${p.montant}"
th:data-date="${p.datePaiement}"
th:data-mode-id="${p.modePaiement.id}"
th:data-licence-id="${p.licence.id}"
th:data-max-amount="${p.licence.getResteAPayer().add(p.montant)}"
onclick="openEditPaiementModal(this.getAttribute('data-paiement-id'), this.getAttribute('data-montant'), this.getAttribute('data-date'), this.getAttribute('data-mode-id'), this.getAttribute('data-licence-id'), this.getAttribute('data-max-amount'))"
class="text-blue-600 hover:text-blue-900 bg-blue-50 hover:bg-blue-100 p-1.5 rounded transition-colors inline-flex items-center"
title="Modifier">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
</svg>
</button>
<form th:action="@{/paiements/{id}/delete(id=${p.id})}" method="post" class="inline m-0" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer ce paiement ?');">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<input type="hidden" name="redirect" value="/paiements" />
<button type="submit" class="text-red-600 hover:text-red-900 bg-red-50 hover:bg-red-100 p-1.5 rounded transition-colors inline-flex items-center" title="Supprimer">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</form>
</div>
</td>
</tr>
</tbody>
</table>
@@ -154,5 +183,51 @@
</div>
</div>
</main>
<!-- Edit Paiement Modal -->
<div id="editPaiementModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-xl bg-white text-left">
<div class="mt-3 text-center">
<h3 class="text-lg leading-6 font-semibold text-gray-900 mb-4">Modifier le Paiement</h3>
<form id="editPaiementForm" method="post" class="space-y-4 text-left">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<input type="hidden" name="redirect" value="/paiements">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Montant (€)</label>
<input type="number" step="0.01" min="0.01" id="editPaiementMontant" name="montant" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="editPaiementDate" name="datePaiement" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Mode de paiement</label>
<select id="editPaiementMode" name="modePaiementId" required class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<option value="">Sélectionnez un mode...</option>
<option th:each="mode : ${modesPaiement}" th:value="${mode.id}" th:text="${mode.nom}"></option>
</select>
</div>
<div class="items-center px-4 py-3 flex justify-end space-x-2">
<button type="button" onclick="closeEditPaiementModal()" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">Annuler</button>
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
<script>
function openEditPaiementModal(paiementId, montant, date, modePaiementId, licenceId, maxAmount) {
document.getElementById('editPaiementModal').classList.remove('hidden');
document.getElementById('editPaiementForm').action = '/paiements/' + paiementId + '/update';
document.getElementById('editPaiementMontant').value = montant;
document.getElementById('editPaiementMontant').max = maxAmount;
document.getElementById('editPaiementDate').value = date;
document.getElementById('editPaiementMode').value = modePaiementId;
}
function closeEditPaiementModal() {
document.getElementById('editPaiementModal').classList.add('hidden');
}
</script>
</body>
</html>