fix: recalculate member category and tariffs dynamically on birthdate change
This commit is contained in:
@@ -61,5 +61,11 @@
|
|||||||
<version>1.77</version>
|
<version>1.77</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</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">
|
<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 value="">Sélectionnez une catégorie...</option>
|
||||||
<option th:each="cat : ${categories}" th:value="${cat.id}"
|
<option th:each="cat : ${categories}" th:value="${cat.id}"
|
||||||
|
th:data-nom="${cat.nom}"
|
||||||
th:data-annee-min="${cat.anneeMin}"
|
th:data-annee-min="${cat.anneeMin}"
|
||||||
th:data-annee-max="${cat.anneeMax}"
|
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>
|
th:text="${cat.nom} + ' (Talange: ' + ${cat.tarifBase} + ' € / Ext: ' + ${cat.tarifExterieur} + ' €)'"></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -443,6 +446,7 @@
|
|||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
const dateInput = document.getElementById('dateNaissance');
|
const dateInput = document.getElementById('dateNaissance');
|
||||||
const repBlock = document.getElementById('representantBlock');
|
const repBlock = document.getElementById('representantBlock');
|
||||||
|
const residentCheckbox = document.getElementById('residentTalange');
|
||||||
|
|
||||||
function calculateAge(birthday) {
|
function calculateAge(birthday) {
|
||||||
const ageDifMs = Date.now() - birthday.getTime();
|
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
|
// Initial check in case it's a validation error reload
|
||||||
updateRepresentantBlock();
|
updateRepresentantBlock();
|
||||||
@@ -475,7 +564,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate price dynamically when residentTalange checkbox changes
|
// Recalculate price dynamically when residentTalange checkbox changes
|
||||||
const residentCheckbox = document.getElementById('residentTalange');
|
|
||||||
if (residentCheckbox) {
|
if (residentCheckbox) {
|
||||||
residentCheckbox.addEventListener('change', function() {
|
residentCheckbox.addEventListener('change', function() {
|
||||||
const isResident = this.checked;
|
const isResident = this.checked;
|
||||||
|
|||||||
Reference in New Issue
Block a user