fix: recalculate member category and tariffs dynamically on birthdate change
This commit is contained in:
@@ -61,5 +61,11 @@
|
||||
<version>1.77</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.astalange.core;
|
||||
|
||||
import com.astalange.core.entity.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class AdherentRecalculationTest {
|
||||
|
||||
@Test
|
||||
public void testLicencePriceCalculationBasedOnResidencyAndCategory() {
|
||||
Adherent adherent = new Adherent();
|
||||
adherent.setNom("Doe");
|
||||
adherent.setPrenom("John");
|
||||
adherent.setDateNaissance(LocalDate.of(2015, 5, 10)); // born 2015
|
||||
adherent.setResidentTalange(true);
|
||||
|
||||
Categorie u11 = new Categorie();
|
||||
u11.setNom("U11");
|
||||
u11.setAnneeMin(2015);
|
||||
u11.setAnneeMax(2015);
|
||||
u11.setTarifBase(new BigDecimal("130.00"));
|
||||
u11.setTarifExterieur(new BigDecimal("160.00"));
|
||||
|
||||
Licence licence = new Licence();
|
||||
licence.setAdherent(adherent);
|
||||
licence.setCategorie(u11);
|
||||
|
||||
// Under residentTalange = true, base price should be 130
|
||||
assertEquals(new BigDecimal("130.00"), licence.getPrixTotal());
|
||||
|
||||
// Under residentTalange = false, base price should be 160
|
||||
adherent.setResidentTalange(false);
|
||||
assertEquals(new BigDecimal("160.00"), licence.getPrixTotal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCategoryUpdateOnBirthdateChangeSimulation() {
|
||||
// Set up adherent
|
||||
Adherent adherent = new Adherent();
|
||||
adherent.setDateNaissance(LocalDate.of(2015, 5, 10)); // 2015 birth year
|
||||
|
||||
Saison saison = new Saison();
|
||||
saison.setNom("2025-2026");
|
||||
saison.setEstActive(true);
|
||||
|
||||
// Set up categories
|
||||
Categorie u11 = new Categorie();
|
||||
u11.setNom("U11");
|
||||
u11.setAnneeMin(2015);
|
||||
u11.setAnneeMax(2015);
|
||||
u11.setSaison(saison);
|
||||
u11.setTarifBase(new BigDecimal("130.00"));
|
||||
|
||||
Categorie u12 = new Categorie();
|
||||
u12.setNom("U12");
|
||||
u12.setAnneeMin(2014);
|
||||
u12.setAnneeMax(2014);
|
||||
u12.setSaison(saison);
|
||||
u12.setTarifBase(new BigDecimal("140.00"));
|
||||
|
||||
List<Categorie> categories = List.of(u11, u12);
|
||||
|
||||
// Licence initially created for U11
|
||||
Licence licence = new Licence();
|
||||
licence.setAdherent(adherent);
|
||||
licence.setCategorie(u11);
|
||||
licence.setSaison(saison);
|
||||
|
||||
// Simulation of date of birth changing to 2014 (U12)
|
||||
adherent.setDateNaissance(LocalDate.of(2014, 5, 10));
|
||||
int newBirthYear = adherent.getDateNaissance().getYear();
|
||||
|
||||
// Simulate Controller's update logic
|
||||
Categorie newCat = categories.stream()
|
||||
.filter(c -> newBirthYear >= c.getAnneeMin() && newBirthYear <= c.getAnneeMax())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
assertNotNull(newCat);
|
||||
assertEquals("U12", newCat.getNom());
|
||||
licence.setCategorie(newCat);
|
||||
|
||||
// Verify the licence's category has updated to U12
|
||||
assertEquals("U12", licence.getCategorie().getNom());
|
||||
// Verify price total is updated dynamically
|
||||
assertEquals(new BigDecimal("140.00"), licence.getPrixTotal());
|
||||
}
|
||||
}
|
||||
@@ -322,8 +322,11 @@
|
||||
<select id="categorieSelect" name="categorieId" 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 une catégorie...</option>
|
||||
<option th:each="cat : ${categories}" th:value="${cat.id}"
|
||||
th:data-nom="${cat.nom}"
|
||||
th:data-annee-min="${cat.anneeMin}"
|
||||
th:data-annee-max="${cat.anneeMax}"
|
||||
th:data-tarif-base="${cat.tarifBase}"
|
||||
th:data-tarif-exterieur="${cat.tarifExterieur}"
|
||||
th:text="${cat.nom} + ' (Talange: ' + ${cat.tarifBase} + ' € / Ext: ' + ${cat.tarifExterieur} + ' €)'"></option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -443,6 +446,7 @@
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const dateInput = document.getElementById('dateNaissance');
|
||||
const repBlock = document.getElementById('representantBlock');
|
||||
const residentCheckbox = document.getElementById('residentTalange');
|
||||
|
||||
function calculateAge(birthday) {
|
||||
const ageDifMs = Date.now() - birthday.getTime();
|
||||
@@ -464,7 +468,92 @@
|
||||
}
|
||||
}
|
||||
|
||||
dateInput.addEventListener('change', updateRepresentantBlock);
|
||||
function updateLicencesCategoryAndPrices() {
|
||||
if (!dateInput.value) return;
|
||||
const dob = new Date(dateInput.value);
|
||||
const birthYear = dob.getFullYear();
|
||||
const isResident = residentCheckbox ? residentCheckbox.checked : true;
|
||||
|
||||
const categories = [];
|
||||
const catSelect = document.getElementById('categorieSelect');
|
||||
if (catSelect) {
|
||||
Array.from(catSelect.options).forEach(opt => {
|
||||
const id = opt.value;
|
||||
const nom = opt.getAttribute('data-nom');
|
||||
const min = parseInt(opt.getAttribute('data-annee-min'));
|
||||
const max = parseInt(opt.getAttribute('data-annee-max'));
|
||||
const tarifBase = parseFloat(opt.getAttribute('data-tarif-base') || '0');
|
||||
const tarifExterieur = parseFloat(opt.getAttribute('data-tarif-exterieur') || '0');
|
||||
if (id) {
|
||||
categories.push({ id, nom, min, max, tarifBase, tarifExterieur });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (categories.length === 0) return;
|
||||
|
||||
const matchingCat = categories.find(c => birthYear >= c.min && birthYear <= c.max);
|
||||
if (!matchingCat) return;
|
||||
|
||||
document.querySelectorAll('.licence-row').forEach(row => {
|
||||
row.setAttribute('data-tarif-base', matchingCat.tarifBase);
|
||||
row.setAttribute('data-tarif-exterieur', matchingCat.tarifExterieur);
|
||||
|
||||
const baseTarif = isResident ? matchingCat.tarifBase : matchingCat.tarifExterieur;
|
||||
const equipContrib = parseFloat(row.getAttribute('data-equipements-contrib') || '0');
|
||||
const totalPaye = parseFloat(row.getAttribute('data-total-paye') || '0');
|
||||
const newTotal = baseTarif + equipContrib;
|
||||
const newReste = Math.max(0, newTotal - totalPaye);
|
||||
|
||||
const catCell = row.querySelector('.cell-tarif-categorie');
|
||||
if (catCell) {
|
||||
const parentTd = catCell.parentNode;
|
||||
const spanName = parentTd.querySelector('span:not(.cell-tarif-categorie)');
|
||||
if (spanName) {
|
||||
spanName.textContent = matchingCat.nom;
|
||||
}
|
||||
catCell.textContent = baseTarif.toFixed(2) + ' € (Catégorie)';
|
||||
}
|
||||
|
||||
const globalCell = row.querySelector('.cell-tarif-global');
|
||||
if (globalCell) {
|
||||
globalCell.textContent = newTotal.toFixed(2) + ' €';
|
||||
}
|
||||
|
||||
const resteCell = row.querySelector('.cell-reste-payer');
|
||||
if (resteCell) {
|
||||
resteCell.textContent = newReste.toFixed(2) + ' €';
|
||||
if (newReste === 0) {
|
||||
resteCell.classList.remove('text-red-600');
|
||||
resteCell.classList.add('text-green-600');
|
||||
} else {
|
||||
resteCell.classList.remove('text-green-600');
|
||||
resteCell.classList.add('text-red-600');
|
||||
}
|
||||
}
|
||||
|
||||
const payerBtn = row.querySelector('.btn-payer');
|
||||
if (payerBtn) {
|
||||
if (newReste > 0) {
|
||||
payerBtn.classList.remove('hidden');
|
||||
const licId = payerBtn.getAttribute('data-licence-id');
|
||||
payerBtn.setAttribute('onclick', `openPaiementModal(${licId}, ${newReste})`);
|
||||
} else {
|
||||
payerBtn.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
const modifierBtn = row.querySelector('button[onclick^="openLicenceModal"]');
|
||||
if (modifierBtn) {
|
||||
modifierBtn.setAttribute('data-categorie-id', matchingCat.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dateInput.addEventListener('change', function() {
|
||||
updateRepresentantBlock();
|
||||
updateLicencesCategoryAndPrices();
|
||||
});
|
||||
|
||||
// Initial check in case it's a validation error reload
|
||||
updateRepresentantBlock();
|
||||
@@ -475,7 +564,6 @@
|
||||
}
|
||||
|
||||
// Recalculate price dynamically when residentTalange checkbox changes
|
||||
const residentCheckbox = document.getElementById('residentTalange');
|
||||
if (residentCheckbox) {
|
||||
residentCheckbox.addEventListener('change', function() {
|
||||
const isResident = this.checked;
|
||||
|
||||
Reference in New Issue
Block a user