Files
api-plr/src/main/java/com/pmumali/ch10_multi/service/ServiceMulti.java
2025-09-17 15:28:14 +00:00

875 lines
39 KiB
Java

package com.pmumali.ch10_multi.service;
import com.pmumali.ch10_multi.model.*;
import com.pmumali.ch10_multi.repository.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ServiceMulti {
private final PariMultiRepository pariRepository;
private final CourseMultiRepository courseRepository;
private final ChevalMultiRepository chevalRepository;
private final PaiementMultiRepository paiementRepository;
private final ResultatCourseMultiRepository resultatRepository;
private final CagnotteMultiRepository cagnotteRepository;
private final ParieurMultiRepository parieurRepository;
private static final Double MISE_BASE = 500.0;
private static final Double MISE_MAX = 200 * MISE_BASE;
private static final Integer NOMBRE_CHEVAUX_MINIMUM = 10;
// Coefficients pour le calcul des rapports (Article 5)
private static final Map<TypeMulti, Integer> COEFFICIENTS = Map.of(
TypeMulti.MULTI_4, 105,
TypeMulti.MULTI_5, 21,
TypeMulti.MULTI_6, 7,
TypeMulti.MULTI_7, 3
);
// Rapports minimum (Article 6)
private static final Map<TypeMulti, Double> RAPPORTS_MINIMUM = Map.of(
TypeMulti.MULTI_4, 1.6,
TypeMulti.MULTI_5, 1.4,
TypeMulti.MULTI_6, 1.2,
TypeMulti.MULTI_7, 1.1
);
@Transactional
public PariMulti placerPari(RequetePariMulti requete) {
// Validation de la mise
if (requete.getMise() < MISE_BASE) {
throw new IllegalArgumentException("La mise doit être au moins " + MISE_BASE + " FCFA");
}
CourseMulti course = courseRepository.findById(requete.getCourseId())
.orElseThrow(() -> new RuntimeException("Course non trouvée"));
// Vérification du nombre minimum de chevaux
if (course.getNombreChevauxPartants() < NOMBRE_CHEVAUX_MINIMUM) {
throw new IllegalArgumentException("La course doit avoir au moins " + NOMBRE_CHEVAUX_MINIMUM + " chevaux partants");
}
List<ChevalMulti> chevaux = chevalRepository.findAllById(requete.getChevalIds());
// Validation du nombre de chevaux selon le type MULTI
validerNombreChevaux(requete.getTypeMulti(), chevaux.size());
// Vérification des non-partants
long nonPartants = chevaux.stream().filter(ChevalMulti::getNonPartant).count();
validerNonPartants(requete.getTypeMulti(), nonPartants);
// Limitation de mise selon l'article 2
Double miseEffective = Math.min(requete.getMise(), MISE_MAX);
ParieurMulti parieur = parieurRepository.findById(requete.getParieurId())
.orElseThrow(() -> new RuntimeException("Parieur non trouvé"));
PariMulti pari = PariMulti.builder()
.course(course)
.chevauxSelectionnes(chevaux)
.mise(miseEffective)
.heurePari(LocalDateTime.now())
.typeMulti(requete.getTypeMulti())
.typeFormule(requete.getTypeFormule())
.parieur(parieur)
.nombreChevauxBase(requete.getNombreChevauxBase())
.build();
return pariRepository.save(pari);
}
private void validerNombreChevaux(TypeMulti typeMulti, int nombreChevaux) {
switch (typeMulti) {
case MULTI_4:
if (nombreChevaux != 4) throw new IllegalArgumentException("MULTI en 4 nécessite exactement 4 chevaux");
break;
case MULTI_5:
if (nombreChevaux != 5) throw new IllegalArgumentException("MULTI en 5 nécessite exactement 5 chevaux");
break;
case MULTI_6:
if (nombreChevaux != 6) throw new IllegalArgumentException("MULTI en 6 nécessite exactement 6 chevaux");
break;
case MULTI_7:
if (nombreChevaux != 7) throw new IllegalArgumentException("MULTI en 7 nécessite exactement 7 chevaux");
break;
}
}
private void validerNonPartants(TypeMulti typeMulti, long nonPartants) {
switch (typeMulti) {
case MULTI_4:
if (nonPartants >= 1) throw new IllegalArgumentException("MULTI en 4 ne permet pas de chevaux non-partants");
break;
case MULTI_5:
if (nonPartants >= 2) throw new IllegalArgumentException("MULTI en 5 permet maximum 1 cheval non-partant");
break;
case MULTI_6:
if (nonPartants >= 3) throw new IllegalArgumentException("MULTI en 6 permet maximum 2 chevaux non-partants");
break;
case MULTI_7:
if (nonPartants >= 4) throw new IllegalArgumentException("MULTI en 7 permet maximum 3 chevaux non-partants");
break;
}
}
@Transactional
public List<ReponsePaiementMulti> calculerPaiements(RequeteResultatMulti requete) {
ResultatCourseMulti resultat = creerResultat(requete);
List<PariMulti> paris = pariRepository.findByCourseId(requete.getCourseId());
List<ReponsePaiementMulti> paiements = new ArrayList<>();
// Vérifier si la course a moins de 10 chevaux (Article 1)
if (resultat.getCourse().getNombreChevauxPartants() < NOMBRE_CHEVAUX_MINIMUM) {
transfererEnCagnotte(resultat);
return paiements;
}
// Calcul de la masse à partager selon l'article 5
Double masseAPartager = calculerMasseAPartager(resultat);
resultat.setMasseAPartager(masseAPartager);
resultatRepository.save(resultat);
// Gérer les cas de dead-heat si nécessaire
TypeDeadHeat typeDeadHeat = detecterDeadHeat(resultat);
if (typeDeadHeat != null) {
return gererDeadHeat(resultat, typeDeadHeat);
}
// Calcul normal des paiements
Map<TypeMulti, List<PariMulti>> parisGagnantsParType = new HashMap<>();
Map<TypeMulti, Integer> nombreParisGagnantsParType = new HashMap<>();
for (PariMulti pari : paris) {
ReponsePaiementMulti paiement = calculerPaiementPari(pari, resultat, masseAPartager);
if (paiement != null) {
paiements.add(paiement);
enregistrerPaiement(pari, paiement);
// Compter les paris gagnants par type
TypeMulti typePari = getTypeMultiFromPaiement(paiement.getTypePaiement());
parisGagnantsParType.computeIfAbsent(typePari, k -> new ArrayList<>()).add(pari);
nombreParisGagnantsParType.put(typePari,
nombreParisGagnantsParType.getOrDefault(typePari, 0) + 1);
}
}
// Appliquer les règles de rapport minimum (Article 6)
appliquerReglesRapportMinimum(paiements, masseAPartager, nombreParisGagnantsParType);
return paiements;
}
private ReponsePaiementMulti calculerPaiementPari(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
List<ChevalMulti> chevauxPari = pari.getChevauxSelectionnes();
long nonPartants = chevauxPari.stream().filter(ChevalMulti::getNonPartant).count();
// Article 4: Gestion des non-partants et transformations
if (doitEtreRembourse(pari.getTypeMulti(), nonPartants)) {
return new ReponsePaiementMulti(pari.getId(), pari.getMise(),
TypePaiementMulti.REMBOURSEMENT, "Remboursement selon article 4");
}
// Transformer les paris avec non-partants (Article 4)
TypeMulti typeTransforme = transformerPari(pari.getTypeMulti(), nonPartants);
List<ChevalMulti> chevauxParticipants = chevauxPari.stream()
.filter(cheval -> !cheval.getNonPartant())
.collect(Collectors.toList());
// Vérifier si les chevaux sont dans les 4 premiers
boolean estGagnant = estCombinaisonGagnante(chevauxParticipants, resultat);
if (estGagnant) {
Double montant = calculerMontantPari(typeTransforme, masseAPartager);
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(typeTransforme);
return new ReponsePaiementMulti(pari.getId(), montant, typePaiement,
"Paiement MULTI " + typeTransforme + " transformé");
}
return null;
}
private boolean doitEtreRembourse(TypeMulti typeMulti, long nonPartants) {
switch (typeMulti) {
case MULTI_4: return nonPartants >= 1;
case MULTI_5: return nonPartants >= 2;
case MULTI_6: return nonPartants >= 3;
case MULTI_7: return nonPartants >= 4;
default: return false;
}
}
private TypeMulti transformerPari(TypeMulti typeOriginal, long nonPartants) {
// Article 4: Transformations des paris avec non-partants
if (nonPartants == 0) return typeOriginal;
switch (typeOriginal) {
case MULTI_5:
return nonPartants == 1 ? TypeMulti.MULTI_4 : typeOriginal;
case MULTI_6:
if (nonPartants == 1) return TypeMulti.MULTI_5;
if (nonPartants == 2) return TypeMulti.MULTI_4;
return typeOriginal;
case MULTI_7:
if (nonPartants == 1) return TypeMulti.MULTI_6;
if (nonPartants == 2) return TypeMulti.MULTI_5;
if (nonPartants == 3) return TypeMulti.MULTI_4;
return typeOriginal;
default:
return typeOriginal;
}
}
private boolean estCombinaisonGagnante(List<ChevalMulti> chevauxPari, ResultatCourseMulti resultat) {
// Vérifier si au moins 4 chevaux sont dans les 4 premiers
long countDansTop4 = chevauxPari.stream()
.filter(cheval -> estDansTop4(cheval, resultat))
.count();
return countDansTop4 >= 4;
}
private boolean estDansTop4(ChevalMulti cheval, ResultatCourseMulti resultat) {
return estDansListe(cheval, resultat.getPremiers()) ||
estDansListe(cheval, resultat.getSeconds()) ||
estDansListe(cheval, resultat.getTroisiemes()) ||
estDansListe(cheval, resultat.getQuatriemes());
}
private boolean estDansListe(ChevalMulti cheval, List<ChevalMulti> liste) {
return liste != null && liste.contains(cheval);
}
private Double calculerMontantPari(TypeMulti typeMulti, Double masseAPartager) {
// Article 5: Calcul basé sur les coefficients
int coefficient = COEFFICIENTS.get(typeMulti);
// Calcul simplifié - en réalité, il faudrait compter le nombre de paris gagnants
return (masseAPartager * 0.25) / coefficient; // 25% de la masse pour chaque type
}
private TypePaiementMulti getTypePaiementFromTypeMulti(TypeMulti typeMulti) {
switch (typeMulti) {
case MULTI_4: return TypePaiementMulti.MULTI_4;
case MULTI_5: return TypePaiementMulti.MULTI_5;
case MULTI_6: return TypePaiementMulti.MULTI_6;
case MULTI_7: return TypePaiementMulti.MULTI_7;
default: return TypePaiementMulti.REMBOURSEMENT;
}
}
private TypeMulti getTypeMultiFromPaiement(TypePaiementMulti typePaiement) {
switch (typePaiement) {
case MULTI_4: return TypeMulti.MULTI_4;
case MULTI_5: return TypeMulti.MULTI_5;
case MULTI_6: return TypeMulti.MULTI_6;
case MULTI_7: return TypeMulti.MULTI_7;
default: return null;
}
}
private void appliquerReglesRapportMinimum(List<ReponsePaiementMulti> paiements, Double masseAPartager,
Map<TypeMulti, Integer> nombreParisGagnantsParType) {
// Article 6: Application des règles de rapport minimum
// Étape 1: Vérifier MULTI_7
Double rapportMulti7 = calculerRapportMoyen(paiements, TypeMulti.MULTI_7);
if (rapportMulti7 < RAPPORTS_MINIMUM.get(TypeMulti.MULTI_7)) {
ajusterRapportsMulti7(paiements, masseAPartager, nombreParisGagnantsParType);
// Recalculer après ajustement
rapportMulti7 = RAPPORTS_MINIMUM.get(TypeMulti.MULTI_7);
}
// Étape 2: Vérifier MULTI_6
Double rapportMulti6 = calculerRapportMoyen(paiements, TypeMulti.MULTI_6);
if (rapportMulti6 < RAPPORTS_MINIMUM.get(TypeMulti.MULTI_6)) {
ajusterRapportsMulti6(paiements, masseAPartager, nombreParisGagnantsParType);
rapportMulti6 = RAPPORTS_MINIMUM.get(TypeMulti.MULTI_6);
}
// Étape 3: Vérifier MULTI_5
Double rapportMulti5 = calculerRapportMoyen(paiements, TypeMulti.MULTI_5);
if (rapportMulti5 < RAPPORTS_MINIMUM.get(TypeMulti.MULTI_5)) {
ajusterRapportsMulti5(paiements, masseAPartager, nombreParisGagnantsParType);
rapportMulti5 = RAPPORTS_MINIMUM.get(TypeMulti.MULTI_5);
}
// Étape 4: Vérifier MULTI_4
Double rapportMulti4 = calculerRapportMoyen(paiements, TypeMulti.MULTI_4);
if (rapportMulti4 < RAPPORTS_MINIMUM.get(TypeMulti.MULTI_4)) {
ajusterRapportsMulti4(paiements, masseAPartager, nombreParisGagnantsParType);
}
}
private Double calculerRapportMoyen(List<ReponsePaiementMulti> paiements, TypeMulti typeMulti) {
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(typeMulti);
List<ReponsePaiementMulti> paiementsType = paiements.stream()
.filter(p -> p.getTypePaiement() == typePaiement)
.collect(Collectors.toList());
if (paiementsType.isEmpty()) {
return 0.0;
}
return paiementsType.stream()
.mapToDouble(ReponsePaiementMulti::getMontant)
.average()
.orElse(0.0);
}
private void ajusterRapportsMulti7(List<ReponsePaiementMulti> paiements, Double masseAPartager,
Map<TypeMulti, Integer> nombreParisGagnantsParType) {
// Article 6.1: Ajustement MULTI_7
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(TypeMulti.MULTI_7);
int nombreGagnants = nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_7, 0);
if (nombreGagnants > 0) {
Double montantTotal = RAPPORTS_MINIMUM.get(TypeMulti.MULTI_7) * nombreGagnants;
// Ajuster les montants
for (ReponsePaiementMulti paiement : paiements) {
if (paiement.getTypePaiement() == typePaiement) {
paiement.setMontant(RAPPORTS_MINIMUM.get(TypeMulti.MULTI_7));
}
}
// Réduire la masse à partager
masseAPartager -= montantTotal;
}
}
private void ajusterRapportsMulti6(List<ReponsePaiementMulti> paiements, Double masseAPartager,
Map<TypeMulti, Integer> nombreParisGagnantsParType) {
// Article 6.2: Ajustement MULTI_6
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(TypeMulti.MULTI_6);
int nombreGagnants = nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_6, 0);
if (nombreGagnants > 0) {
// Calculer le rapport de base
Double rapportBase = masseAPartager / (nombreGagnants * 1 +
nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_5, 0) * 3 +
nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_4, 0) * 15);
// Ajuster les montants
for (ReponsePaiementMulti paiement : paiements) {
TypeMulti typeMulti = getTypeMultiFromPaiement(paiement.getTypePaiement());
if (typeMulti == TypeMulti.MULTI_6) {
paiement.setMontant(rapportBase);
} else if (typeMulti == TypeMulti.MULTI_5) {
paiement.setMontant(rapportBase * 3);
} else if (typeMulti == TypeMulti.MULTI_4) {
paiement.setMontant(rapportBase * 15);
}
}
}
}
private void ajusterRapportsMulti5(List<ReponsePaiementMulti> paiements, Double masseAPartager,
Map<TypeMulti, Integer> nombreParisGagnantsParType) {
// Article 6.3: Ajustement MULTI_5
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(TypeMulti.MULTI_5);
int nombreGagnants = nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_5, 0);
if (nombreGagnants > 0) {
// Calculer le rapport de base
Double rapportBase = masseAPartager / (nombreGagnants * 1 +
nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_4, 0) * 5);
// Ajuster les montants
for (ReponsePaiementMulti paiement : paiements) {
TypeMulti typeMulti = getTypeMultiFromPaiement(paiement.getTypePaiement());
if (typeMulti == TypeMulti.MULTI_5) {
paiement.setMontant(rapportBase);
} else if (typeMulti == TypeMulti.MULTI_4) {
paiement.setMontant(rapportBase * 5);
}
}
}
}
private void ajusterRapportsMulti4(List<ReponsePaiementMulti> paiements, Double masseAPartager,
Map<TypeMulti, Integer> nombreParisGagnantsParType) {
// Article 6.4: Ajustement MULTI_4
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(TypeMulti.MULTI_4);
int nombreGagnants = nombreParisGagnantsParType.getOrDefault(TypeMulti.MULTI_4, 0);
if (nombreGagnants > 0) {
// Calculer le rapport de base
Double rapportBase = masseAPartager / nombreGagnants;
// Forcer le rapport minimum si nécessaire
if (rapportBase < RAPPORTS_MINIMUM.get(TypeMulti.MULTI_4)) {
rapportBase = RAPPORTS_MINIMUM.get(TypeMulti.MULTI_4);
}
// Ajuster les montants
for (ReponsePaiementMulti paiement : paiements) {
if (paiement.getTypePaiement() == typePaiement) {
paiement.setMontant(rapportBase);
}
}
}
}
// Méthodes pour le calcul des combinaisons (Article 7)
public CalculCombinaisonMulti calculerCombinaison(TypeMulti typeMulti, TypeFormuleMulti typeFormule,
Integer nombreChevauxTotal, Integer nombreChevauxBase) {
Integer nombreCombinaisons = calculerNombreCombinaisons(typeMulti, typeFormule, nombreChevauxTotal, nombreChevauxBase);
Double valeurMise = nombreCombinaisons * MISE_BASE;
return CalculCombinaisonMulti.builder()
.typeMulti(typeMulti)
.typeFormule(typeFormule)
.nombreChevauxTotal(nombreChevauxTotal)
.nombreChevauxBase(nombreChevauxBase)
.nombreCombinaisons(nombreCombinaisons)
.valeurMise(valeurMise)
.build();
}
private Integer calculerNombreCombinaisons(TypeMulti typeMulti, TypeFormuleMulti typeFormule,
Integer n, Integer p) {
if (typeFormule == TypeFormuleMulti.UNITAIRE) {
return 1;
}
// Implémentation des formules de combinaison selon l'article 7
switch (typeMulti) {
case MULTI_4:
return calculerCombinaisonsMulti4(typeFormule, n, p);
case MULTI_5:
return calculerCombinaisonsMulti5(typeFormule, n, p);
case MULTI_6:
return calculerCombinaisonsMulti6(typeFormule, n, p);
case MULTI_7:
return calculerCombinaisonsMulti7(typeFormule, n, p);
default:
return 0;
}
}
private Integer calculerCombinaisonsMulti4(TypeFormuleMulti typeFormule, Integer n, Integer p) {
if (typeFormule == TypeFormuleMulti.CHAMP_TOTAL) {
if (p == 3) return n - 3;
if (p == 2) return (n - 2) * (n - 3) / 2;
if (p == 1) return (n - 1) * (n - 2) * (n - 3) / 6;
} else {
if (p == 3) return p;
if (p == 2) return p * (p - 1) / 2;
if (p == 1) return p * (p - 1) * (p - 2) / 6;
}
return 0;
}
private Integer calculerCombinaisonsMulti5(TypeFormuleMulti typeFormule, Integer n, Integer p) {
if (typeFormule == TypeFormuleMulti.CHAMP_TOTAL) {
if (p == 4) return n - 4;
if (p == 3) return (n - 3) * (n - 4) / 2;
if (p == 2) return (n - 2) * (n - 3) * (n - 4) / 6;
if (p == 1) return (n - 1) * (n - 2) * (n - 3) * (n - 4) / 24;
} else {
if (p == 4) return p;
if (p == 3) return p * (p - 1) / 2;
if (p == 2) return p * (p - 1) * (p - 2) / 6;
if (p == 1) return p * (p - 1) * (p - 2) * (p - 3) / 24;
}
return 0;
}
private Integer calculerCombinaisonsMulti6(TypeFormuleMulti typeFormule, Integer n, Integer p) {
if (typeFormule == TypeFormuleMulti.CHAMP_TOTAL) {
if (p == 5) return n - 5;
if (p == 4) return (n - 4) * (n - 5) / 2;
if (p == 3) return (n - 3) * (n - 4) * (n - 5) / 6;
if (p == 2) return (n - 2) * (n - 3) * (n - 4) * (n - 5) / 24;
if (p == 1) return (n - 1) * (n - 2) * (n - 3) * (n - 4) * (n - 5) / 120;
} else {
if (p == 5) return p;
if (p == 4) return p * (p - 1) / 2;
if (p == 3) return p * (p - 1) * (p - 2) / 6;
if (p == 2) return p * (p - 1) * (p - 2) * (p - 3) / 24;
if (p == 1) return p * (p - 1) * (p - 2) * (p - 3) * (p - 4) / 120;
}
return 0;
}
private Integer calculerCombinaisonsMulti7(TypeFormuleMulti typeFormule, Integer n, Integer p) {
if (typeFormule == TypeFormuleMulti.CHAMP_TOTAL) {
if (p == 6) return n - 6;
if (p == 5) return (n - 5) * (n - 6) / 2;
if (p == 4) return (n - 4) * (n - 5) * (n - 6) / 6;
if (p == 3) return (n - 3) * (n - 4) * (n - 5) * (n - 6) / 24;
if (p == 2) return (n - 2) * (n - 3) * (n - 4) * (n - 5) * (n - 6) / 120;
if (p == 1) return (n - 1) * (n - 2) * (n - 3) * (n - 4) * (n - 5) * (n - 6) / 720;
} else {
if (p == 6) return p;
if (p == 5) return p * (p - 1) / 2;
if (p == 4) return p * (p - 1) * (p - 2) / 6;
if (p == 3) return p * (p - 1) * (p - 2) * (p - 3) / 24;
if (p == 2) return p * (p - 1) * (p - 2) * (p - 3) * (p - 4) / 120;
if (p == 1) return p * (p - 1) * (p - 2) * (p - 3) * (p - 4) * (p - 5) / 720;
}
return 0;
}
// Gestion des dead-heat (Article 3)
private TypeDeadHeat detecterDeadHeat(ResultatCourseMulti resultat) {
if (resultat.getPremiers().size() >= 4) return TypeDeadHeat.QUATRE_PREMIERS_OU_PLUS;
if (resultat.getPremiers().size() >= 3 && resultat.getQuatriemes().size() >= 1)
return TypeDeadHeat.TROIS_PREMIERS_UN_QUATRIEME;
if (resultat.getPremiers().size() >= 2 && resultat.getTroisiemes().size() >= 2)
return TypeDeadHeat.DEUX_PREMIERS_DEUX_TROISIEMES;
if (resultat.getPremiers().size() >= 2 && resultat.getTroisiemes().size() == 1 && resultat.getQuatriemes().size() >= 1)
return TypeDeadHeat.DEUX_PREMIERS_UN_TROISIEME_UN_QUATRIEME;
if (resultat.getSeconds().size() >= 3)
return TypeDeadHeat.TROIS_SECONDS_OU_PLUS;
if (resultat.getSeconds().size() >= 2 && resultat.getQuatriemes().size() >= 1)
return TypeDeadHeat.DEUX_SECONDS_UN_QUATRIEME;
if (resultat.getTroisiemes().size() >= 2)
return TypeDeadHeat.DEUX_TROISIEMES_OU_PLUS;
if (resultat.getQuatriemes().size() >= 2)
return TypeDeadHeat.DEUX_QUATRIEMES_OU_PLUS;
return null;
}
private List<ReponsePaiementMulti> gererDeadHeat(ResultatCourseMulti resultat, TypeDeadHeat typeDeadHeat) {
List<PariMulti> paris = pariRepository.findByCourseId(resultat.getCourse().getId());
List<ReponsePaiementMulti> paiements = new ArrayList<>();
Double masseAPartager = resultat.getMasseAPartager();
for (PariMulti pari : paris) {
ReponsePaiementMulti paiement = calculerPaiementDeadHeat(pari, resultat, masseAPartager, typeDeadHeat);
if (paiement != null) {
paiements.add(paiement);
enregistrerPaiement(pari, paiement);
}
}
return paiements;
}
private ReponsePaiementMulti calculerPaiementDeadHeat(PariMulti pari, ResultatCourseMulti resultat,
Double masseAPartager, TypeDeadHeat typeDeadHeat) {
// Implémentation spécifique pour chaque type de dead-heat
switch (typeDeadHeat) {
case QUATRE_PREMIERS_OU_PLUS:
return gererDeadHeatQuatrePremiers(pari, resultat, masseAPartager);
case TROIS_PREMIERS_UN_QUATRIEME:
return gererDeadHeatTroisPremiersUnQuatrieme(pari, resultat, masseAPartager);
case DEUX_PREMIERS_DEUX_TROISIEMES:
return gererDeadHeatDeuxPremiersDeuxTroisiemes(pari, resultat, masseAPartager);
case DEUX_PREMIERS_UN_TROISIEME_UN_QUATRIEME:
return gererDeadHeatDeuxPremiersUnTroisiemeUnQuatrieme(pari, resultat, masseAPartager);
case TROIS_SECONDS_OU_PLUS:
return gererDeadHeatTroisSeconds(pari, resultat, masseAPartager);
case DEUX_SECONDS_UN_QUATRIEME:
return gererDeadHeatDeuxSecondsUnQuatrieme(pari, resultat, masseAPartager);
case DEUX_TROISIEMES_OU_PLUS:
return gererDeadHeatDeuxTroisiemes(pari, resultat, masseAPartager);
case DEUX_QUATRIEMES_OU_PLUS:
return gererDeadHeatDeuxQuatriemes(pari, resultat, masseAPartager);
default:
return calculerPaiementPari(pari, resultat, masseAPartager);
}
}
private ReponsePaiementMulti gererDeadHeatQuatrePremiers(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3a: Dead-heat de 4+ chevaux premiers
List<ChevalMulti> chevauxPari = pari.getChevauxSelectionnes();
// Vérifier si tous les chevaux du pari sont parmi les premiers
if (chevauxPari.stream().allMatch(cheval -> estDansListe(cheval, resultat.getPremiers()))) {
int nombreCombinaisons = calculerNombreCombinaisonsDeadHeat(resultat.getPremiers().size(), 4);
Double montant = masseAPartager / nombreCombinaisons;
return new ReponsePaiementMulti(pari.getId(), montant,
TypePaiementMulti.MULTI_4, "Dead-Heat 4+ premiers");
}
return null;
}
private ReponsePaiementMulti gererDeadHeatTroisPremiersUnQuatrieme(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3b: Dead-heat de 3 premiers + 1+ quatrième
List<ChevalMulti> chevauxPari = pari.getChevauxSelectionnes();
boolean troisPremiersOk = chevauxPari.subList(0, 3).stream()
.allMatch(cheval -> estDansListe(cheval, resultat.getPremiers()));
boolean quatriemeOk = estDansListe(chevauxPari.get(3), resultat.getQuatriemes());
if (troisPremiersOk && quatriemeOk) {
int nombreCombinaisons = resultat.getPremiers().size() * resultat.getQuatriemes().size();
Double montant = (masseAPartager * 0.4) / (nombreCombinaisons * 6); // 40% pour l'ordre exact, 6 permutations
return new ReponsePaiementMulti(pari.getId(), montant,
TypePaiementMulti.MULTI_4, "Dead-Heat 3 premiers + 1 quatrième");
}
return null;
}
// Implémentations similaires pour les autres types de dead-heat...
private ReponsePaiementMulti gererDeadHeatDeuxPremiersDeuxTroisiemes(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3c: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 2.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxPremiersUnTroisiemeUnQuatrieme(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3d: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti gererDeadHeatTroisSeconds(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3e: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 2.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxSecondsUnQuatrieme(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3f: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxTroisiemes(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3g: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxQuatriemes(PariMulti pari, ResultatCourseMulti resultat, Double masseAPartager) {
// Article 3h: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti calculerPaiementGeneriqueDeadHeat(PariMulti pari, ResultatCourseMulti resultat,
Double masseAPartager, Double coefficient) {
// Méthode générique pour les dead-heat avec coefficient spécifique
List<ChevalMulti> chevauxPari = pari.getChevauxSelectionnes();
if (chevauxPari.stream().allMatch(cheval ->
estDansListe(cheval, resultat.getPremiers()) ||
estDansListe(cheval, resultat.getSeconds()) ||
estDansListe(cheval, resultat.getTroisiemes()) ||
estDansListe(cheval, resultat.getQuatriemes()))) {
int nombreCombinaisons = compterCombinaisonsGagnantesDeadHeat(resultat);
Double montant = (masseAPartager * 0.4) / (nombreCombinaisons * coefficient);
return new ReponsePaiementMulti(pari.getId(), montant,
TypePaiementMulti.MULTI_4, "Dead-Heat générique");
}
return null;
}
private int calculerNombreCombinaisonsDeadHeat(int nombreChevaux, int prise) {
// Calcul des combinaisons C(n, k)
if (prise == 0) return 1;
if (nombreChevaux < prise) return 0;
int resultat = 1;
for (int i = 1; i <= prise; i++) {
resultat = resultat * (nombreChevaux - i + 1) / i;
}
return resultat;
}
private int compterCombinaisonsGagnantesDeadHeat(ResultatCourseMulti resultat) {
// Comptage simplifié des combinaisons gagnantes
// Implémentation réelle dépendrait de la structure exacte du dead-heat
return 1;
}
// Gestion de la cagnotte (Articles 9 et 10)
private void transfererEnCagnotte(ResultatCourseMulti resultat) {
CagnotteMulti cagnotte = CagnotteMulti.builder()
.montant(resultat.getMasseAPartager())
.dateCreation(LocalDateTime.now())
.utilisee(false)
.courseSourceId(resultat.getCourse().getId())
.build();
cagnotteRepository.save(cagnotte);
}
@Transactional
public void utiliserCagnotte(Long cagnotteId, Long courseId) {
CagnotteMulti cagnotte = cagnotteRepository.findById(cagnotteId)
.orElseThrow(() -> new RuntimeException("Cagnotte non trouvée"));
if (cagnotte.getUtilisee()) {
throw new RuntimeException("Cagnotte déjà utilisée");
}
cagnotte.setUtilisee(true);
cagnotte.setDateUtilisation(LocalDateTime.now());
cagnotteRepository.save(cagnotte);
// Ajouter le montant à la masse à partager de la course
ResultatCourseMulti resultat = resultatRepository.findByCourseId(courseId);
if (resultat != null) {
resultat.setMasseAPartager(resultat.getMasseAPartager() + cagnotte.getMontant());
resultatRepository.save(resultat);
}
}
// Méthodes utilitaires
private Double calculerMasseAPartager(ResultatCourseMulti resultat) {
// Article 5: MAP = RNET - MREMB - PRELEV
return resultat.getRecetteNette() -
resultat.getMontantRembourse() -
resultat.getPrelevementsLegaux();
}
private ResultatCourseMulti creerResultat(RequeteResultatMulti requete) {
CourseMulti course = courseRepository.findById(requete.getCourseId())
.orElseThrow(() -> new RuntimeException("Course non trouvée"));
return ResultatCourseMulti.builder()
.course(course)
.premiers(chevalRepository.findAllById(requete.getPremiersIds()))
.seconds(chevalRepository.findAllById(requete.getSecondsIds()))
.troisiemes(chevalRepository.findAllById(requete.getTroisiemesIds()))
.quatriemes(chevalRepository.findAllById(requete.getQuatriemesIds()))
.ordreArrivee(chevalRepository.findAllById(requete.getOrdreArriveeIds()))
.recetteNette(requete.getRecetteNette())
.prelevementsLegaux(requete.getPrelevementsLegaux())
.montantRembourse(0.0)
.build();
}
private void enregistrerPaiement(PariMulti pari, ReponsePaiementMulti reponse) {
PaiementMulti paiement = PaiementMulti.builder()
.pari(pari)
.montant(reponse.getMontant())
.heurePaiement(LocalDateTime.now())
.typePaiement(reponse.getTypePaiement())
.build();
paiementRepository.save(paiement);
}
// Méthodes supplémentaires pour la gestion des cas particuliers (Article 9)
@Transactional
public List<ReponsePaiementMulti> gererCasParticuliers(RequeteResultatMulti requete) {
ResultatCourseMulti resultat = creerResultat(requete);
List<PariMulti> paris = pariRepository.findByCourseId(requete.getCourseId());
// Article 9: Cas où il n'y a aucune mise sur la combinaison gagnante
if (!existeParisGagnants(paris, resultat)) {
return appliquerReglesDegradation(resultat, paris);
}
return calculerPaiements(requete);
}
private boolean existeParisGagnants(List<PariMulti> paris, ResultatCourseMulti resultat) {
for (PariMulti pari : paris) {
if (estCombinaisonGagnante(pari.getChevauxSelectionnes(), resultat)) {
return true;
}
}
return false;
}
private List<ReponsePaiementMulti> appliquerReglesDegradation(ResultatCourseMulti resultat, List<PariMulti> paris) {
// Article 9: Application des règles de dégradation
List<ReponsePaiementMulti> paiements = new ArrayList<>();
Double masseAPartager = resultat.getMasseAPartager();
// Essayer successivement les combinaisons dégradées
for (int i = 0; i < 4; i++) {
for (PariMulti pari : paris) {
ReponsePaiementMulti paiement = calculerPaiementDegrade(pari, resultat, masseAPartager, i);
if (paiement != null) {
paiements.add(paiement);
enregistrerPaiement(pari, paiement);
return paiements; // On s'arrête au premier succès
}
}
}
// Si aucune combinaison dégradée ne fonctionne, transférer en cagnotte
transfererEnCagnotte(resultat);
return paiements;
}
private ReponsePaiementMulti calculerPaiementDegrade(PariMulti pari, ResultatCourseMulti resultat,
Double masseAPartager, int niveauDegradation) {
// Implémentation des règles de dégradation selon l'article 9
List<ChevalMulti> chevauxPari = pari.getChevauxSelectionnes();
// Vérifier différents patterns de dégradation
boolean estGagnant = false;
switch (niveauDegradation) {
case 0: // 1er, 2ème, 3ème, 5ème
estGagnant = estPatternDegrade(chevauxPari, resultat, 0, 1, 2, 4);
break;
case 1: // 1er, 2ème, 4ème, 5ème
estGagnant = estPatternDegrade(chevauxPari, resultat, 0, 1, 3, 4);
break;
case 2: // 1er, 3ème, 4ème, 5ème
estGagnant = estPatternDegrade(chevauxPari, resultat, 0, 2, 3, 4);
break;
case 3: // 2ème, 3ème, 4ème, 5ème
estGagnant = estPatternDegrade(chevauxPari, resultat, 1, 2, 3, 4);
break;
}
if (estGagnant) {
Double montant = calculerMontantPari(pari.getTypeMulti(), masseAPartager);
TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(pari.getTypeMulti());
return new ReponsePaiementMulti(pari.getId(), montant, typePaiement,
"Paiement dégradé niveau " + niveauDegradation);
}
return null;
}
private boolean estPatternDegrade(List<ChevalMulti> chevauxPari, ResultatCourseMulti resultat,
int pos1, int pos2, int pos3, int pos4) {
// Vérifier un pattern spécifique de dégradation
if (resultat.getOrdreArrivee().size() < 5) return false;
ChevalMulti cheval1 = resultat.getOrdreArrivee().get(pos1);
ChevalMulti cheval2 = resultat.getOrdreArrivee().get(pos2);
ChevalMulti cheval3 = resultat.getOrdreArrivee().get(pos3);
ChevalMulti cheval4 = resultat.getOrdreArrivee().get(pos4);
return chevauxPari.contains(cheval1) &&
chevauxPari.contains(cheval2) &&
chevauxPari.contains(cheval3) &&
chevauxPari.contains(cheval4);
}
// Méthodes pour les statistiques et rapports
public Map<TypeMulti, Integer> getStatistiquesParis(Long courseId) {
List<PariMulti> paris = pariRepository.findByCourseId(courseId);
return paris.stream()
.collect(Collectors.groupingBy(PariMulti::getTypeMulti,
Collectors.summingInt(p -> 1)));
}
public Map<TypeFormuleMulti, Integer> getStatistiquesFormules(Long courseId) {
List<PariMulti> paris = pariRepository.findByCourseId(courseId);
return paris.stream()
.collect(Collectors.groupingBy(PariMulti::getTypeFormule,
Collectors.summingInt(p -> 1)));
}
public Double getMasseAPartagerCourse(Long courseId) {
ResultatCourseMulti resultat = resultatRepository.findByCourseId(courseId);
return resultat != null ? resultat.getMasseAPartager() : 0.0;
}
public List<CagnotteMulti> getCagnottesDisponibles() {
return cagnotteRepository.findByUtilisee(false);
}
public List<PaiementMulti> getHistoriquePaiementsParieur(Long parieurId) {
List<PariMulti> paris = pariRepository.findByParieurId(parieurId);
return paris.stream()
.flatMap(pari -> paiementRepository.findByPariId(pari.getId()).stream())
.collect(Collectors.toList());
}
}