Initial commit - nettoyage et initialisation

This commit is contained in:
2026-05-19 22:01:18 +02:00
commit ee85d38c43
51 changed files with 2333 additions and 0 deletions
@@ -0,0 +1,97 @@
package com.astalange.core.entity;
import com.astalange.core.validation.ValidRepresentantLegal;
import jakarta.persistence.*;
import java.time.LocalDate;
@Entity
@Table(name = "adherent")
@ValidRepresentantLegal
public class Adherent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String nom;
@Column(nullable = false, length = 100)
private String prenom;
@Column(name = "date_naissance", nullable = false)
@org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE)
private LocalDate dateNaissance;
@Column(length = 20)
private String telephone;
@Column(length = 100)
private String email;
@Column(columnDefinition = "TEXT")
private String adresse;
@Column(name = "representant_legal", length = 150)
private String representantLegal;
private boolean residentTalange = true;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getPrenom() { return prenom; }
public void setPrenom(String prenom) { this.prenom = prenom; }
public LocalDate getDateNaissance() { return dateNaissance; }
public void setDateNaissance(LocalDate dateNaissance) { this.dateNaissance = dateNaissance; }
public String getTelephone() { return telephone; }
public void setTelephone(String telephone) { this.telephone = telephone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getAdresse() { return adresse; }
public void setAdresse(String adresse) { this.adresse = adresse; }
public String getRepresentantLegal() { return representantLegal; }
public void setRepresentantLegal(String representantLegal) { this.representantLegal = representantLegal; }
public boolean isResidentTalange() { return residentTalange; }
public void setResidentTalange(boolean residentTalange) { this.residentTalange = residentTalange; }
@OneToMany(mappedBy = "adherent", cascade = CascadeType.ALL)
private java.util.List<Licence> licences = new java.util.ArrayList<>();
public java.util.List<Licence> getLicences() { return licences; }
public void setLicences(java.util.List<Licence> licences) { this.licences = licences; }
@Transient
public Licence getLicenceActuelle() {
if (licences == null || licences.isEmpty()) {
return null;
}
// Assuming the last added license is the current one (highest ID or latest)
return licences.get(licences.size() - 1);
}
@Transient
public String getStatutPaiement() {
Licence current = getLicenceActuelle();
if (current == null) {
return "Aucune licence";
}
java.math.BigDecimal reste = current.getResteAPayer();
if (reste.compareTo(java.math.BigDecimal.ZERO) == 0) {
return "À jour";
} else {
return "Reste : " + reste + "";
}
}
}
@@ -0,0 +1,36 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "app_user")
@Getter
@Setter
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private boolean enabled = true;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
@@ -0,0 +1,48 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "categorie")
public class Categorie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 20)
private String nom;
@Column(name = "annee_min")
private Integer anneeMin;
@Column(name = "annee_max")
private Integer anneeMax;
@Column(name = "tarif_base", nullable = false, precision = 10, scale = 2)
private BigDecimal tarifBase;
@Column(name = "tarif_exterieur", nullable = false, precision = 10, scale = 2)
private BigDecimal tarifExterieur;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public Integer getAnneeMin() { return anneeMin; }
public void setAnneeMin(Integer anneeMin) { this.anneeMin = anneeMin; }
public Integer getAnneeMax() { return anneeMax; }
public void setAnneeMax(Integer anneeMax) { this.anneeMax = anneeMax; }
public BigDecimal getTarifBase() { return tarifBase; }
public void setTarifBase(BigDecimal tarifBase) { this.tarifBase = tarifBase; }
public BigDecimal getTarifExterieur() { return tarifExterieur; }
public void setTarifExterieur(BigDecimal tarifExterieur) { this.tarifExterieur = tarifExterieur; }
}
@@ -0,0 +1,49 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "dotation")
public class Dotation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "licence_id", nullable = false)
private Licence licence;
@ManyToOne(optional = false)
@JoinColumn(name = "equipement_id", nullable = false)
private Equipement equipement;
@Column(length = 10)
private String taille;
@Column(length = 50)
private String flocage;
@Column(nullable = false)
private Boolean fourni = false;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Licence getLicence() { return licence; }
public void setLicence(Licence licence) { this.licence = licence; }
public Equipement getEquipement() { return equipement; }
public void setEquipement(Equipement equipement) { this.equipement = equipement; }
public String getTaille() { return taille; }
public void setTaille(String taille) { this.taille = taille; }
public String getFlocage() { return flocage; }
public void setFlocage(String flocage) { this.flocage = flocage; }
public Boolean getFourni() { return fourni; }
public void setFourni(Boolean fourni) { this.fourni = fourni; }
}
@@ -0,0 +1,35 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "equipement")
public class Equipement {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String nom;
@Column(columnDefinition = "TEXT")
private String description;
@Column(nullable = false)
private Boolean obligatoire = false;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Boolean getObligatoire() { return obligatoire; }
public void setObligatoire(Boolean obligatoire) { this.obligatoire = obligatoire; }
}
@@ -0,0 +1,91 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "licence")
public class Licence {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "adherent_id", nullable = false)
private Adherent adherent;
@ManyToOne(optional = false)
@JoinColumn(name = "categorie_id", nullable = false)
private Categorie categorie;
@Column(nullable = false, length = 20)
private String saison;
@Column(nullable = false, length = 50)
private String etat;
@Column(name = "numero_licence", length = 50)
private String numeroLicence;
@OneToMany(mappedBy = "licence", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Paiement> paiements = new ArrayList<>();
// Logic for Reste A Payer
@Transient
public BigDecimal getResteAPayer() {
if (categorie == null) return BigDecimal.ZERO;
BigDecimal prixTotal = (adherent != null && adherent.isResidentTalange())
? categorie.getTarifBase()
: categorie.getTarifExterieur();
if (prixTotal == null) prixTotal = BigDecimal.ZERO;
if (paiements == null || paiements.isEmpty()) {
return prixTotal;
}
BigDecimal totalPaye = paiements.stream()
.map(Paiement::getMontant)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal reste = prixTotal.subtract(totalPaye);
return reste.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : reste;
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Adherent getAdherent() { return adherent; }
public void setAdherent(Adherent adherent) { this.adherent = adherent; }
public Categorie getCategorie() { return categorie; }
public void setCategorie(Categorie categorie) { this.categorie = categorie; }
public String getSaison() { return saison; }
public void setSaison(String saison) { this.saison = saison; }
public String getEtat() { return etat; }
public void setEtat(String etat) { this.etat = etat; }
public List<Paiement> getPaiements() { return paiements; }
public void setPaiements(List<Paiement> paiements) { this.paiements = paiements; }
public String getNumeroLicence() { return numeroLicence; }
public void setNumeroLicence(String numeroLicence) { this.numeroLicence = numeroLicence; }
public void addPaiement(Paiement paiement) {
paiements.add(paiement);
paiement.setLicence(this);
}
public void removePaiement(Paiement paiement) {
paiements.remove(paiement);
paiement.setLicence(null);
}
}
@@ -0,0 +1,23 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "mode_paiement")
public class ModePaiement {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String nom;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
}
@@ -0,0 +1,45 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDate;
@Entity
@Table(name = "paiement")
public class Paiement {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "licence_id", nullable = false)
private Licence licence;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal montant;
@Column(name = "date_paiement", nullable = false)
private LocalDate datePaiement;
@ManyToOne(optional = false)
@JoinColumn(name = "mode_paiement_id", nullable = false)
private ModePaiement modePaiement;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Licence getLicence() { return licence; }
public void setLicence(Licence licence) { this.licence = licence; }
public BigDecimal getMontant() { return montant; }
public void setMontant(BigDecimal montant) { this.montant = montant; }
public LocalDate getDatePaiement() { return datePaiement; }
public void setDatePaiement(LocalDate datePaiement) { this.datePaiement = datePaiement; }
public ModePaiement getModePaiement() { return modePaiement; }
public void setModePaiement(ModePaiement modePaiement) { this.modePaiement = modePaiement; }
}
@@ -0,0 +1,19 @@
package com.astalange.core.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "role")
@Getter
@Setter
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
}
@@ -0,0 +1,12 @@
package com.astalange.core.repository;
import com.astalange.core.entity.Adherent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AdherentRepository extends JpaRepository<Adherent, Long> {
List<Adherent> findTop5ByOrderByIdDesc();
}
@@ -0,0 +1,9 @@
package com.astalange.core.repository;
import com.astalange.core.entity.AppUser;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface AppUserRepository extends JpaRepository<AppUser, Long> {
Optional<AppUser> findByUsername(String username);
}
@@ -0,0 +1,9 @@
package com.astalange.core.repository;
import com.astalange.core.entity.Categorie;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CategorieRepository extends JpaRepository<Categorie, Long> {
}
@@ -0,0 +1,9 @@
package com.astalange.core.repository;
import com.astalange.core.entity.Dotation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DotationRepository extends JpaRepository<Dotation, Long> {
}
@@ -0,0 +1,9 @@
package com.astalange.core.repository;
import com.astalange.core.entity.Equipement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EquipementRepository extends JpaRepository<Equipement, Long> {
}
@@ -0,0 +1,19 @@
package com.astalange.core.repository;
import com.astalange.core.entity.Licence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.util.List;
@Repository
public interface LicenceRepository extends JpaRepository<Licence, Long> {
List<Licence> findByAdherentId(Long adherentId);
long countByEtat(String etat);
@Query("SELECT SUM(c.tarifBase) FROM Licence l JOIN l.categorie c")
BigDecimal sumTarifBase();
}
@@ -0,0 +1,12 @@
package com.astalange.core.repository;
import com.astalange.core.entity.ModePaiement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ModePaiementRepository extends JpaRepository<ModePaiement, Long> {
Optional<ModePaiement> findByNom(String nom);
}
@@ -0,0 +1,14 @@
package com.astalange.core.repository;
import com.astalange.core.entity.Paiement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
@Repository
public interface PaiementRepository extends JpaRepository<Paiement, Long> {
@Query("SELECT SUM(p.montant) FROM Paiement p")
BigDecimal sumMontant();
}
@@ -0,0 +1,38 @@
package com.astalange.core.security;
import com.astalange.core.entity.AppUser;
import com.astalange.core.repository.AppUserRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final AppUserRepository userRepository;
public UserDetailsServiceImpl(AppUserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AppUser appUser = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new User(
appUser.getUsername(),
appUser.getPassword(),
appUser.isEnabled(),
true, true, true,
appUser.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList())
);
}
}
@@ -0,0 +1,25 @@
package com.astalange.core.validation;
import com.astalange.core.entity.Adherent;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.Period;
public class RepresentantLegalValidator implements ConstraintValidator<ValidRepresentantLegal, Adherent> {
@Override
public boolean isValid(Adherent adherent, ConstraintValidatorContext context) {
if (adherent == null || adherent.getDateNaissance() == null) {
return true; // Let @NotNull handle the null cases
}
int age = Period.between(adherent.getDateNaissance(), LocalDate.now()).getYears();
if (age < 18) {
return adherent.getRepresentantLegal() != null && !adherent.getRepresentantLegal().trim().isEmpty();
}
return true;
}
}
@@ -0,0 +1,15 @@
package com.astalange.core.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = RepresentantLegalValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidRepresentantLegal {
String message() default "Le représentant légal est obligatoire pour les adhérents mineurs.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,75 @@
CREATE TABLE role (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL
);
CREATE TABLE app_user (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE user_role (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id),
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES app_user(id) ON DELETE CASCADE,
CONSTRAINT fk_role FOREIGN KEY (role_id) REFERENCES role(id) ON DELETE CASCADE
);
CREATE TABLE adherent (
id BIGSERIAL PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
date_naissance DATE NOT NULL,
telephone VARCHAR(20),
email VARCHAR(100),
adresse TEXT,
representant_legal VARCHAR(150)
);
CREATE TABLE categorie (
id BIGSERIAL PRIMARY KEY,
nom VARCHAR(20) UNIQUE NOT NULL,
annee_min INT,
annee_max INT,
tarif_base DECIMAL(10,2) NOT NULL
);
CREATE TABLE licence (
id BIGSERIAL PRIMARY KEY,
adherent_id BIGINT NOT NULL,
categorie_id BIGINT NOT NULL,
saison VARCHAR(20) NOT NULL,
etat VARCHAR(50) NOT NULL,
CONSTRAINT fk_licence_adherent FOREIGN KEY (adherent_id) REFERENCES adherent(id) ON DELETE CASCADE,
CONSTRAINT fk_licence_categorie FOREIGN KEY (categorie_id) REFERENCES categorie(id)
);
CREATE TABLE paiement (
id BIGSERIAL PRIMARY KEY,
licence_id BIGINT NOT NULL,
montant DECIMAL(10,2) NOT NULL,
date_paiement DATE NOT NULL,
mode_paiement VARCHAR(50) NOT NULL,
CONSTRAINT fk_paiement_licence FOREIGN KEY (licence_id) REFERENCES licence(id) ON DELETE CASCADE
);
CREATE TABLE equipement (
id BIGSERIAL PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
description TEXT,
obligatoire BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE dotation (
id BIGSERIAL PRIMARY KEY,
licence_id BIGINT NOT NULL,
equipement_id BIGINT NOT NULL,
taille VARCHAR(10),
flocage VARCHAR(50),
fourni BOOLEAN NOT NULL DEFAULT FALSE,
CONSTRAINT fk_dotation_licence FOREIGN KEY (licence_id) REFERENCES licence(id) ON DELETE CASCADE,
CONSTRAINT fk_dotation_equipement FOREIGN KEY (equipement_id) REFERENCES equipement(id)
);
@@ -0,0 +1,12 @@
CREATE TABLE mode_paiement (
id BIGSERIAL PRIMARY KEY,
nom VARCHAR(50) UNIQUE NOT NULL
);
INSERT INTO mode_paiement (nom) VALUES ('Espèces'), ('Chèque'), ('Pass Sport'), ('Virement');
ALTER TABLE paiement ADD COLUMN mode_paiement_id BIGINT;
ALTER TABLE paiement ADD CONSTRAINT fk_paiement_mode FOREIGN KEY (mode_paiement_id) REFERENCES mode_paiement(id);
ALTER TABLE paiement DROP COLUMN mode_paiement;
@@ -0,0 +1,23 @@
INSERT INTO role (name) VALUES ('ROLE_ADMIN'), ('ROLE_USER');
-- Password is "Talange2026!" hashed with Argon2id
INSERT INTO app_user (username, password, enabled) VALUES ('Ucef', '$argon2id$v=19$m=65536,t=3,p=4$q29E/VM1esZVxo9HufgILQ$Nzb+rwwsZUMy04BbewYum2A8L8ujs9VZj9VJ/609Am0', true);
INSERT INTO user_role (user_id, role_id) VALUES ((SELECT id FROM app_user WHERE username = 'Ucef'), (SELECT id FROM role WHERE name = 'ROLE_ADMIN'));
INSERT INTO categorie (nom, annee_min, annee_max, tarif_base) VALUES
('U6', 2020, 2020, 100.00),
('U7', 2019, 2019, 100.00),
('U8', 2018, 2018, 120.00),
('U9', 2017, 2017, 120.00),
('U10', 2016, 2016, 130.00),
('U11', 2015, 2015, 130.00),
('U12', 2014, 2014, 140.00),
('U13', 2013, 2013, 140.00),
('Senior', 1900, 2005, 180.00);
INSERT INTO equipement (nom, obligatoire) VALUES
('Maillot', true),
('Short', true),
('Chaussettes', true),
('Survêtement', false);
@@ -0,0 +1,8 @@
-- Ajouter la colonne pour savoir si l'adhérent est résident de Talange (par défaut oui pour les existants)
ALTER TABLE adherent ADD COLUMN resident_talange BOOLEAN DEFAULT TRUE;
-- Ajouter le tarif extérieur aux catégories
ALTER TABLE categorie ADD COLUMN tarif_exterieur DECIMAL(10,2);
-- Par défaut, mettre le tarif extérieur égal au tarif de base pour ne pas casser les licences existantes
UPDATE categorie SET tarif_exterieur = tarif_base;
@@ -0,0 +1 @@
ALTER TABLE licence ADD COLUMN numero_licence VARCHAR(50);
@@ -0,0 +1,11 @@
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
public class HashGen {
public static void main(String[] args) {
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("Talange2026!");
System.out.println("HASH_RESULT_START");
System.out.println(result);
System.out.println("HASH_RESULT_END");
}
}