Refactor: Move equipment price and mandatory status to Categorie relationship
- Created CategorieEquipement entity to act as a join table between Category and Equipment. - Moved 'obligatoire' and 'prix' (formerly prixContribution) fields from Equipement to the new CategorieEquipement entity. - Added Flyway migrations V14 and V15 to update the database schema and migrate existing data. - Updated Categorie UI to use a select dropdown and price input for managing category-specific equipment configurations. - Replaced 'equipement.obligatoire' and 'equipement.prixContribution' calls with their new category-level equivalents in logic and templates. - Updated pricing calculation in Licence entity to source prices dynamically from the Categorie configuration. - Updated Dotation generation logic to sync with category requirements automatically.
This commit is contained in:
@@ -2,6 +2,8 @@ package com.astalange.core.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "categorie")
|
||||
@@ -30,6 +32,9 @@ public class Categorie {
|
||||
@JoinColumn(name = "saison_id", nullable = false)
|
||||
private Saison saison;
|
||||
|
||||
@OneToMany(mappedBy = "categorie", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<CategorieEquipement> categorieEquipements = new ArrayList<>();
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public Long getId() { return id; }
|
||||
@@ -52,4 +57,29 @@ public class Categorie {
|
||||
|
||||
public Saison getSaison() { return saison; }
|
||||
public void setSaison(Saison saison) { this.saison = saison; }
|
||||
|
||||
public List<CategorieEquipement> getCategorieEquipements() { return categorieEquipements; }
|
||||
public void setCategorieEquipements(List<CategorieEquipement> categorieEquipements) { this.categorieEquipements = categorieEquipements; }
|
||||
|
||||
@Transient
|
||||
public boolean hasEquipement(Long equipementId) {
|
||||
if (categorieEquipements == null) return false;
|
||||
return categorieEquipements.stream().anyMatch(ce -> ce.getEquipement() != null && ce.getEquipement().getId().equals(equipementId));
|
||||
}
|
||||
|
||||
@Transient
|
||||
public boolean isEquipementObligatoire(Long equipementId) {
|
||||
if (categorieEquipements == null) return false;
|
||||
return categorieEquipements.stream().anyMatch(ce -> ce.getEquipement() != null && ce.getEquipement().getId().equals(equipementId) && Boolean.TRUE.equals(ce.getObligatoire()));
|
||||
}
|
||||
|
||||
@Transient
|
||||
public BigDecimal getEquipementPrix(Long equipementId) {
|
||||
if (categorieEquipements == null) return BigDecimal.ZERO;
|
||||
return categorieEquipements.stream()
|
||||
.filter(ce -> ce.getEquipement() != null && ce.getEquipement().getId().equals(equipementId))
|
||||
.findFirst()
|
||||
.map(CategorieEquipement::getPrix)
|
||||
.orElse(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.astalange.core.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "categorie_equipement")
|
||||
public class CategorieEquipement {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "categorie_id", nullable = false)
|
||||
private Categorie categorie;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "equipement_id", nullable = false)
|
||||
private Equipement equipement;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean obligatoire = false;
|
||||
|
||||
@Column(nullable = false, precision = 10, scale = 2)
|
||||
private java.math.BigDecimal prix = java.math.BigDecimal.ZERO;
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public java.math.BigDecimal getPrix() { return prix; }
|
||||
public void setPrix(java.math.BigDecimal prix) { this.prix = prix; }
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public Categorie getCategorie() { return categorie; }
|
||||
public void setCategorie(Categorie categorie) { this.categorie = categorie; }
|
||||
|
||||
public Equipement getEquipement() { return equipement; }
|
||||
public void setEquipement(Equipement equipement) { this.equipement = equipement; }
|
||||
|
||||
public Boolean getObligatoire() { return obligatoire; }
|
||||
public void setObligatoire(Boolean obligatoire) { this.obligatoire = obligatoire; }
|
||||
}
|
||||
@@ -17,11 +17,6 @@ public class Equipement {
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean obligatoire = false;
|
||||
|
||||
@Column(name = "prix_contribution", nullable = false, precision = 10, scale = 2)
|
||||
private BigDecimal prixContribution = BigDecimal.ZERO;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "saison_id", nullable = false)
|
||||
@@ -38,12 +33,6 @@ public class Equipement {
|
||||
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; }
|
||||
|
||||
public BigDecimal getPrixContribution() { return prixContribution; }
|
||||
public void setPrixContribution(BigDecimal prixContribution) { this.prixContribution = prixContribution; }
|
||||
|
||||
public Saison getSaison() { return saison; }
|
||||
public void setSaison(Saison saison) { this.saison = saison; }
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class Licence {
|
||||
if (dotations != null) {
|
||||
for (Dotation dot : dotations) {
|
||||
if (dot.getChoisi() && dot.getEquipement() != null) {
|
||||
BigDecimal price = dot.getEquipement().getPrixContribution();
|
||||
BigDecimal price = categorie.getEquipementPrix(dot.getEquipement().getId());
|
||||
if (price != null) {
|
||||
prixTotal = prixTotal.add(price);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.List;
|
||||
@Repository
|
||||
public interface LicenceRepository extends JpaRepository<Licence, Long> {
|
||||
List<Licence> findByAdherentId(Long adherentId);
|
||||
|
||||
List<Licence> findBySaisonAndCategorie(com.astalange.core.entity.Saison saison, com.astalange.core.entity.Categorie categorie);
|
||||
|
||||
long countByEtat(String etat);
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.astalange.core.service;
|
||||
|
||||
import com.astalange.core.entity.*;
|
||||
import com.astalange.core.repository.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CategorieService {
|
||||
|
||||
private final CategorieRepository categorieRepository;
|
||||
private final SaisonRepository saisonRepository;
|
||||
private final EquipementRepository equipementRepository;
|
||||
private final LicenceRepository licenceRepository;
|
||||
|
||||
public CategorieService(CategorieRepository categorieRepository, SaisonRepository saisonRepository, EquipementRepository equipementRepository, LicenceRepository licenceRepository) {
|
||||
this.categorieRepository = categorieRepository;
|
||||
this.saisonRepository = saisonRepository;
|
||||
this.equipementRepository = equipementRepository;
|
||||
this.licenceRepository = licenceRepository;
|
||||
}
|
||||
|
||||
public record EquipementConfig(boolean obligatoire, java.math.BigDecimal prix) {}
|
||||
|
||||
@Transactional
|
||||
public void saveCategorie(Categorie categorie, java.util.Map<Long, EquipementConfig> equipementConfigs) {
|
||||
Saison activeSaison;
|
||||
Categorie existing = null;
|
||||
if (categorie.getId() == null) {
|
||||
activeSaison = saisonRepository.findByEstActiveTrue()
|
||||
.orElseThrow(() -> new IllegalStateException("Aucune saison active trouvée"));
|
||||
categorie.setSaison(activeSaison);
|
||||
} else {
|
||||
final Long catId = categorie.getId();
|
||||
existing = categorieRepository.findById(catId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid category ID: " + catId));
|
||||
categorie.setSaison(existing.getSaison());
|
||||
activeSaison = existing.getSaison();
|
||||
}
|
||||
|
||||
// Handle CategorieEquipement
|
||||
if (existing != null) {
|
||||
categorie.getCategorieEquipements().clear();
|
||||
categorie.getCategorieEquipements().addAll(existing.getCategorieEquipements());
|
||||
}
|
||||
|
||||
List<CategorieEquipement> nouveauxEquipements = new ArrayList<>();
|
||||
if (equipementConfigs != null) {
|
||||
for (java.util.Map.Entry<Long, EquipementConfig> entry : equipementConfigs.entrySet()) {
|
||||
Long eqId = entry.getKey();
|
||||
EquipementConfig config = entry.getValue();
|
||||
Equipement eq = equipementRepository.findById(eqId).orElse(null);
|
||||
if (eq != null) {
|
||||
CategorieEquipement ce = new CategorieEquipement();
|
||||
ce.setCategorie(categorie);
|
||||
ce.setEquipement(eq);
|
||||
ce.setObligatoire(config.obligatoire());
|
||||
ce.setPrix(config.prix() != null ? config.prix() : java.math.BigDecimal.ZERO);
|
||||
nouveauxEquipements.add(ce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
categorie.getCategorieEquipements().clear();
|
||||
categorie.getCategorieEquipements().addAll(nouveauxEquipements);
|
||||
|
||||
categorie = categorieRepository.save(categorie);
|
||||
|
||||
// Mettre à jour les adhérents (via les licences de la saison actuelle)
|
||||
List<Licence> licences = licenceRepository.findBySaisonAndCategorie(activeSaison, categorie);
|
||||
for (Licence licence : licences) {
|
||||
updateDotationsForLicence(licence, nouveauxEquipements);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDotationsForLicence(Licence licence, List<CategorieEquipement> categorieEquipements) {
|
||||
List<Dotation> currentDotations = licence.getDotations();
|
||||
List<Dotation> toRemove = new ArrayList<>();
|
||||
|
||||
// Remove dotations for equipements no longer in the category
|
||||
for (Dotation d : currentDotations) {
|
||||
boolean found = false;
|
||||
for (CategorieEquipement ce : categorieEquipements) {
|
||||
if (ce.getEquipement().getId().equals(d.getEquipement().getId())) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
toRemove.add(d);
|
||||
}
|
||||
}
|
||||
currentDotations.removeAll(toRemove);
|
||||
|
||||
// Add missing dotations
|
||||
for (CategorieEquipement ce : categorieEquipements) {
|
||||
boolean found = false;
|
||||
for (Dotation d : currentDotations) {
|
||||
if (ce.getEquipement().getId().equals(d.getEquipement().getId())) {
|
||||
found = true;
|
||||
if (Boolean.TRUE.equals(ce.getObligatoire()) && !Boolean.TRUE.equals(d.getChoisi())) {
|
||||
d.setChoisi(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
Dotation newDotation = new Dotation();
|
||||
newDotation.setLicence(licence);
|
||||
newDotation.setEquipement(ce.getEquipement());
|
||||
newDotation.setChoisi(ce.getObligatoire()); // By default checked if obligatoire
|
||||
newDotation.setFourni(false);
|
||||
currentDotations.add(newDotation);
|
||||
}
|
||||
}
|
||||
|
||||
licenceRepository.save(licence);
|
||||
}
|
||||
}
|
||||
@@ -53,14 +53,15 @@ public class SaisonService {
|
||||
private void dupliquerParametrages(Saison ancienneSaison, Saison nouvelleSaison) {
|
||||
// 1. Dupliquer les équipements
|
||||
List<Equipement> anciensEquipements = equipementRepository.findBySaison(ancienneSaison);
|
||||
java.util.Map<Long, Equipement> oldToNewEquipementMap = new java.util.HashMap<>();
|
||||
|
||||
for (Equipement ancien : anciensEquipements) {
|
||||
Equipement nouvelEquipement = new Equipement();
|
||||
nouvelEquipement.setNom(ancien.getNom());
|
||||
nouvelEquipement.setDescription(ancien.getDescription());
|
||||
nouvelEquipement.setObligatoire(ancien.getObligatoire());
|
||||
nouvelEquipement.setPrixContribution(ancien.getPrixContribution());
|
||||
nouvelEquipement.setSaison(nouvelleSaison);
|
||||
equipementRepository.save(nouvelEquipement);
|
||||
nouvelEquipement = equipementRepository.save(nouvelEquipement);
|
||||
oldToNewEquipementMap.put(ancien.getId(), nouvelEquipement);
|
||||
}
|
||||
|
||||
// 2. Dupliquer les catégories (avec incrémentation de l'année)
|
||||
@@ -78,6 +79,22 @@ public class SaisonService {
|
||||
nouvelleCategorie.setTarifBase(ancienne.getTarifBase());
|
||||
nouvelleCategorie.setTarifExterieur(ancienne.getTarifExterieur());
|
||||
nouvelleCategorie.setSaison(nouvelleSaison);
|
||||
|
||||
// Dupliquer les relations avec les équipements
|
||||
if (ancienne.getCategorieEquipements() != null) {
|
||||
for (com.astalange.core.entity.CategorieEquipement oldCatEq : ancienne.getCategorieEquipements()) {
|
||||
Equipement newEq = oldToNewEquipementMap.get(oldCatEq.getEquipement().getId());
|
||||
if (newEq != null) {
|
||||
com.astalange.core.entity.CategorieEquipement newCatEq = new com.astalange.core.entity.CategorieEquipement();
|
||||
newCatEq.setCategorie(nouvelleCategorie);
|
||||
newCatEq.setEquipement(newEq);
|
||||
newCatEq.setObligatoire(oldCatEq.getObligatoire());
|
||||
newCatEq.setPrix(oldCatEq.getPrix());
|
||||
nouvelleCategorie.getCategorieEquipements().add(newCatEq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
categorieRepository.save(nouvelleCategorie);
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE categorie_equipement (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
categorie_id BIGINT NOT NULL,
|
||||
equipement_id BIGINT NOT NULL,
|
||||
obligatoire BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT fk_cat_equip_categorie FOREIGN KEY (categorie_id) REFERENCES categorie(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_cat_equip_equipement FOREIGN KEY (equipement_id) REFERENCES equipement(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Migrer les données existantes : associer tous les équipements d'une saison à toutes les catégories de la même saison
|
||||
-- et transférer la valeur "obligatoire" de l'équipement vers la nouvelle table de liaison
|
||||
INSERT INTO categorie_equipement (categorie_id, equipement_id, obligatoire)
|
||||
SELECT c.id, e.id, e.obligatoire
|
||||
FROM categorie c
|
||||
JOIN equipement e ON c.saison_id = e.saison_id;
|
||||
|
||||
-- Supprimer la colonne "obligatoire" de la table équipement
|
||||
ALTER TABLE equipement DROP COLUMN obligatoire;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE categorie_equipement ADD COLUMN prix NUMERIC(10,2) NOT NULL DEFAULT 0;
|
||||
|
||||
-- Migrer les données existantes
|
||||
UPDATE categorie_equipement ce
|
||||
SET prix = e.prix_contribution
|
||||
FROM equipement e
|
||||
WHERE ce.equipement_id = e.id;
|
||||
|
||||
ALTER TABLE equipement DROP COLUMN IF EXISTS prix_contribution;
|
||||
Reference in New Issue
Block a user