Compare commits

4 Commits

Author SHA1 Message Date
ucef 194986f880 feat: pré-sélection de la catégorie par âge et sexe lors de la création d'une licence
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m22s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m18s
2026-06-29 09:28:38 +02:00
ucef d834b8d0d3 fix: ajout du script de migration Flyway pour les nouveaux champs
- Script V30__add_ancienne_categorie_and_sexe.sql pour ajouter les champs en DB.

- Mise à jour des colonnes telephone, ancienne_categorie et sexe.
2026-06-29 09:14:18 +02:00
ucef e3abe53eb0 feat: amélioration des formulaires d'inscription et pré-inscription
- Rendre le champ téléphone obligatoire (entités et UI).

- Ajout du champ 'Ancienne catégorie' conditionné par la saisie de l'ancien club.

- Synchronisation de l'ancienne catégorie entre la pré-inscription et la fiche adhérent.
2026-06-29 09:03:45 +02:00
ucef 348d6e6ccf chore: bump version to 1.4-SNAPSHOT
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m17s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m28s
2026-06-25 11:05:23 +02:00
14 changed files with 176 additions and 22 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>as-talange-parent</artifactId>
<groupId>com.astalange</groupId>
<version>1.0-SNAPSHOT</version>
<version>1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>as-talange-parent</artifactId>
<groupId>com.astalange</groupId>
<version>1.0-SNAPSHOT</version>
<version>1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -35,7 +35,7 @@ public class Adherent {
@Column(nullable = false, length = 100)
private String email;
@Column(length = 20)
@Column(nullable = false, length = 20)
private String telephone;
@Column(name = "representant_legal", length = 150)
@@ -47,6 +47,9 @@ public class Adherent {
@Column(name = "ancien_club", length = 150)
private String ancienClub;
@Column(name = "ancienne_categorie", length = 50)
private String ancienneCategorie;
private boolean residentTalange = true;
@Column(nullable = false, length = 10)
@@ -99,6 +102,9 @@ public class Adherent {
public String getAncienClub() { return ancienClub; }
public void setAncienClub(String ancienClub) { this.ancienClub = ancienClub; }
public String getAncienneCategorie() { return ancienneCategorie; }
public void setAncienneCategorie(String ancienneCategorie) { this.ancienneCategorie = ancienneCategorie; }
public boolean isResidentTalange() { return residentTalange; }
public void setResidentTalange(boolean residentTalange) { this.residentTalange = residentTalange; }
@@ -18,6 +18,9 @@ public class Categorie {
@Column(nullable = false, length = 20)
private String nom;
@Column(nullable = false, length = 10)
private String sexe = "MASCULIN";
@Column(name = "annee_min")
private Integer anneeMin;
@@ -45,6 +48,9 @@ public class Categorie {
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getSexe() { return sexe; }
public void setSexe(String sexe) { this.sexe = sexe; }
public Integer getAnneeMin() { return anneeMin; }
public void setAnneeMin(Integer anneeMin) { this.anneeMin = anneeMin; }
@@ -33,7 +33,7 @@ public class PreInscription {
@Column(nullable = false, length = 150)
private String email;
@Column(length = 20)
@Column(nullable = false, length = 20)
private String telephone;
@Column(name = "representant_legal", length = 150)
@@ -58,6 +58,9 @@ public class PreInscription {
@Column(name = "ancien_club", length = 150)
private String ancienClub;
@Column(name = "ancienne_categorie", length = 50)
private String ancienneCategorie = "NA";
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@@ -106,4 +109,7 @@ public class PreInscription {
public String getAncienClub() { return ancienClub; }
public void setAncienClub(String ancienClub) { this.ancienClub = ancienClub; }
public String getAncienneCategorie() { return ancienneCategorie; }
public void setAncienneCategorie(String ancienneCategorie) { this.ancienneCategorie = ancienneCategorie; }
}
@@ -46,6 +46,7 @@ public class PreInscriptionService {
adherent.setRepresentantLegal(pre.getRepresentantLegal());
adherent.setTypeDemande(pre.getTypeDemande());
adherent.setAncienClub(pre.getAncienClub());
adherent.setAncienneCategorie(pre.getAncienneCategorie());
// Par défaut
adherent.setSexe("MASCULIN");
adherent.setTypeMaillot("JOUEUR");
@@ -0,0 +1,10 @@
ALTER TABLE adherent ADD COLUMN ancienne_categorie VARCHAR(50);
ALTER TABLE pre_inscription ADD COLUMN ancienne_categorie VARCHAR(50) DEFAULT 'NA';
ALTER TABLE categorie ADD COLUMN sexe VARCHAR(10) DEFAULT 'MASCULIN' NOT NULL;
UPDATE adherent SET telephone = '0000000000' WHERE telephone IS NULL;
ALTER TABLE adherent ALTER COLUMN telephone SET NOT NULL;
UPDATE pre_inscription SET telephone = '0000000000' WHERE telephone IS NULL;
ALTER TABLE pre_inscription ALTER COLUMN telephone SET NOT NULL;
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>as-talange-parent</artifactId>
<groupId>com.astalange</groupId>
<version>1.0-SNAPSHOT</version>
<version>1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -120,8 +120,8 @@
<!-- Téléphone -->
<div>
<label for="telephone" class="block text-sm font-medium text-gray-700 mb-1">Téléphone</label>
<input type="tel" id="telephone" th:field="*{telephone}"
<label for="telephone" class="block text-sm font-medium text-gray-700 mb-1">Téléphone <span class="text-red-500">*</span></label>
<input type="tel" id="telephone" th:field="*{telephone}" required
class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
th:classappend="${#fields.hasErrors('telephone')} ? 'border-red-500' : ''">
<p th:if="${#fields.hasErrors('telephone')}" th:errors="*{telephone}" class="mt-1 text-xs text-red-600"></p>
@@ -166,6 +166,29 @@
</div>
</div>
<div id="ancienneCategorieBlock" class="grid grid-cols-1 md:grid-cols-2 gap-6 hidden-block mt-6">
<div>
<label for="ancienneCategorie" class="block text-sm font-medium text-gray-700 mb-1">Ancienne catégorie <span class="text-red-500">*</span></label>
<select id="ancienneCategorie" th:field="*{ancienneCategorie}"
class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors">
<option value="NA">NA (Je ne sais plus)</option>
<option value="U6">U6</option>
<option value="U7">U7</option>
<option value="U8">U8</option>
<option value="U9">U9</option>
<option value="U10">U10</option>
<option value="U11">U11</option>
<option value="U12">U12</option>
<option value="U13">U13</option>
<option value="U14">U14</option>
<option value="U15">U15</option>
<option value="U16">U16</option>
<option value="U17">U17</option>
<option value="Senior">Senior</option>
</select>
</div>
</div>
<!-- Removed Adresse -->
<div class="pt-4 flex justify-end space-x-3 border-t border-gray-100">
@@ -408,6 +431,7 @@
th:data-nom="${cat.nom}"
th:data-annee-min="${cat.anneeMin}"
th:data-annee-max="${cat.anneeMax}"
th:data-sexe="${cat.sexe}"
th:data-tarif-base="${cat.tarifBase}"
th:data-tarif-exterieur="${cat.tarifExterieur}"
th:text="${cat.nom} + ' (Talange: ' + ${cat.tarifBase} + ' € / Ext: ' + ${cat.tarifExterieur} + ' €)'"></option>
@@ -581,25 +605,41 @@
document.getElementById('typeLicenceSelect').value = '';
// Pré-sélection de la catégorie selon l'année de naissance
// Pré-sélection de la catégorie selon l'année de naissance et le sexe
const dateNaissanceStr = document.getElementById('dateNaissance').value;
const adherentSexe = document.getElementById('sexe') ? document.getElementById('sexe').value : 'MASCULIN';
let foundCat = false;
let fallbackCatId = '';
let preSelectedCatId = '';
if (dateNaissanceStr) {
const birthYear = new Date(dateNaissanceStr).getFullYear();
Array.from(catSelect.options).forEach(opt => {
if (!opt.value) return;
const min = parseInt(opt.getAttribute('data-annee-min'));
const max = parseInt(opt.getAttribute('data-annee-max'));
const catSexe = opt.getAttribute('data-sexe') || 'MASCULIN';
if (!isNaN(min) && !isNaN(max) && birthYear >= min && birthYear <= max) {
if (catSexe === adherentSexe) {
catSelect.value = opt.value;
preSelectedCatId = opt.value;
foundCat = true;
}
if (catSexe === 'MASCULIN') {
fallbackCatId = opt.value;
}
}
});
}
if (!foundCat && fallbackCatId) {
catSelect.value = fallbackCatId;
preSelectedCatId = fallbackCatId;
foundCat = true;
}
if (!foundCat) {
catSelect.value = '';
}
@@ -681,11 +721,33 @@
}
}
const ancienClubInput = document.getElementById('ancienClub');
const ancienneCategorieBlock = document.getElementById('ancienneCategorieBlock');
const ancienneCategorieSelect = document.getElementById('ancienneCategorie');
if (ancienClubInput) {
ancienClubInput.addEventListener('input', function() {
if (this.value.trim().length > 0) {
ancienneCategorieBlock.classList.remove('hidden-block');
ancienneCategorieSelect.required = true;
} else {
ancienneCategorieBlock.classList.add('hidden-block');
ancienneCategorieSelect.required = false;
}
});
if (ancienClubInput.value.trim().length > 0) {
ancienneCategorieBlock.classList.remove('hidden-block');
ancienneCategorieSelect.required = true;
}
}
function updateLicencesCategoryAndPrices() {
if (!dateInput.value) return;
const dob = new Date(dateInput.value);
const birthYear = dob.getFullYear();
const isResident = residentCheckbox ? residentCheckbox.checked : true;
const adherentSexe = document.getElementById('sexe') ? document.getElementById('sexe').value : 'MASCULIN';
const categories = [];
const catSelect = document.getElementById('categorieSelect');
@@ -695,17 +757,21 @@
const nom = opt.getAttribute('data-nom');
const min = parseInt(opt.getAttribute('data-annee-min'));
const max = parseInt(opt.getAttribute('data-annee-max'));
const sexe = opt.getAttribute('data-sexe') || 'MASCULIN';
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 });
categories.push({ id, nom, min, max, sexe, tarifBase, tarifExterieur });
}
});
}
if (categories.length === 0) return;
const matchingCat = categories.find(c => birthYear >= c.min && birthYear <= c.max);
let matchingCat = categories.find(c => birthYear >= c.min && birthYear <= c.max && c.sexe === adherentSexe);
if (!matchingCat) {
matchingCat = categories.find(c => birthYear >= c.min && birthYear <= c.max && c.sexe === 'MASCULIN');
}
if (!matchingCat) return;
document.querySelectorAll('.licence-row').forEach(row => {
@@ -853,7 +919,10 @@
}
if (sexeSelect) {
sexeSelect.addEventListener('change', updateEquipmentStyleLabels);
sexeSelect.addEventListener('change', function() {
updateEquipmentStyleLabels();
updateLicencesCategoryAndPrices();
});
}
if (typeMaillotSelect) {
typeMaillotSelect.addEventListener('change', updateEquipmentStyleLabels);
@@ -22,10 +22,19 @@
<form th:action="@{/categories}" th:object="${categorie}" method="post" class="space-y-6">
<input type="hidden" th:field="*{id}" />
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nom (ex: U15)</label>
<input type="text" th:field="*{nom}" 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">Catégorie</label>
<select th:field="*{sexe}" 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="MASCULIN">Masculine</option>
<option value="FEMININ">Féminine</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
@@ -43,7 +43,10 @@
<td colspan="6" class="py-8 text-center text-gray-500">Aucune catégorie n'est paramétrée.</td>
</tr>
<tr th:each="cat : ${categories}" class="hover:bg-gray-50 transition-colors">
<td class="py-4 px-6 font-medium text-gray-900" th:text="${cat.nom}">U15</td>
<td class="py-4 px-6">
<div class="font-medium text-gray-900" th:text="${cat.nom}">U15</div>
<div class="text-xs text-gray-500" th:text="${cat.sexe == 'FEMININ' ? 'Féminine' : 'Masculine'}">Masculine</div>
</td>
<td class="py-4 px-6 text-center text-gray-600" th:text="${cat.anneeMin}">2010</td>
<td class="py-4 px-6 text-center text-gray-600" th:text="${cat.anneeMax}">2011</td>
<td class="py-4 px-6 text-right font-medium text-blue-600" th:text="${cat.tarifBase + ' €'}">100.00 €</td>
@@ -52,7 +52,27 @@
<div id="nouvelleLicenceBlock" class="hidden space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Ancien club</label>
<input type="text" th:field="*{ancienClub}" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none" placeholder="Nom du club (laisser vide si aucun)">
<input type="text" id="ancienClub" th:field="*{ancienClub}" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none" placeholder="Nom du club (laisser vide si aucun)">
</div>
<div id="ancienneCategorieBlock" class="hidden">
<label class="block text-sm font-medium text-gray-700 mb-1">Ancienne catégorie <span class="text-red-500">*</span></label>
<select id="ancienneCategorie" th:field="*{ancienneCategorie}" 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="NA">NA (Je ne sais plus)</option>
<option value="U6">U6</option>
<option value="U7">U7</option>
<option value="U8">U8</option>
<option value="U9">U9</option>
<option value="U10">U10</option>
<option value="U11">U11</option>
<option value="U12">U12</option>
<option value="U13">U13</option>
<option value="U14">U14</option>
<option value="U15">U15</option>
<option value="U16">U16</option>
<option value="U17">U17</option>
<option value="Senior">Senior</option>
</select>
</div>
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 rounded-lg">
@@ -113,8 +133,8 @@
<input type="email" th:field="*{email}" 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">Téléphone</label>
<input type="tel" th:field="*{telephone}" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<label class="block text-sm font-medium text-gray-700 mb-1">Téléphone <span class="text-red-500">*</span></label>
<input type="tel" th:field="*{telephone}" 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>
@@ -184,6 +204,27 @@
nouvelleLicenceBlock.classList.remove('hidden');
}
const ancienClubInput = document.getElementById('ancienClub');
const ancienneCategorieBlock = document.getElementById('ancienneCategorieBlock');
const ancienneCategorieSelect = document.getElementById('ancienneCategorie');
if (ancienClubInput) {
ancienClubInput.addEventListener('input', function() {
if (this.value.trim().length > 0) {
ancienneCategorieBlock.classList.remove('hidden');
ancienneCategorieSelect.required = true;
} else {
ancienneCategorieBlock.classList.add('hidden');
ancienneCategorieSelect.required = false;
}
});
// Initial check
if (ancienClubInput.value.trim().length > 0) {
ancienneCategorieBlock.classList.remove('hidden');
ancienneCategorieSelect.required = true;
}
}
document.getElementById('dateNaissance').addEventListener('change', function() {
const dateInput = this.value;
const repBlock = document.getElementById('representantBlock');
+1 -1
View File
@@ -13,7 +13,7 @@
<groupId>com.astalange</groupId>
<artifactId>as-talange-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>as-talange-parent</name>