416 lines
18 KiB
Java
416 lines
18 KiB
Java
package com.pmumali.ch3_jumeleplace.service;
|
|
|
|
import com.pmumali.ch3_jumeleplace.model.Course;
|
|
import com.pmumali.ch3_jumeleplace.model.Gains;
|
|
import com.pmumali.ch3_jumeleplace.model.PariJumelePlace;
|
|
import com.pmumali.ch3_jumeleplace.model.ResultatCourse;
|
|
import com.pmumali.ch3_jumeleplace.repository.*;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
@Service
|
|
@Transactional
|
|
public class GainsService {
|
|
|
|
@Autowired
|
|
private PariJumelePlaceRepository pariRepository;
|
|
|
|
@Autowired
|
|
private GainsRepository gainsRepository;
|
|
|
|
@Autowired
|
|
private CourseRepository courseRepository;
|
|
|
|
@Autowired
|
|
private ChevalRepository chevalRepository;
|
|
|
|
@Autowired
|
|
private ResultatCourseRepository resultatRepository;
|
|
|
|
private static final double RAPPORT_MINIMUM = 1.1;
|
|
private static final double PRELEVEMENTS = 0.15; // 15% de prélèvements
|
|
|
|
public Gains calculerGains(Long courseId, ResultatCourse resultat) {
|
|
Course course = courseRepository.findById(courseId)
|
|
.orElseThrow(() -> new RuntimeException("Course non trouvée"));
|
|
|
|
List<PariJumelePlace> tousParis = pariRepository.findByCourseId(courseId);
|
|
|
|
// Calcul de la recette nette (Article 5)
|
|
double totalMises = tousParis.stream().mapToDouble(PariJumelePlace::getMise).sum();
|
|
double recetteNette = totalMises;
|
|
|
|
// Calcul des remboursements (Article 4)
|
|
double montantRemboursements = calculerRemboursements(tousParis);
|
|
|
|
// Masse à partager (Article 5)
|
|
double masseAPartager = recetteNette - montantRemboursements - (recetteNette * PRELEVEMENTS);
|
|
|
|
// Déterminer les combinaisons payables
|
|
List<CombinaisonPayable> combinaisonsPayables = determinerCombinaisonsPayables(resultat);
|
|
|
|
// Calcul des rapports
|
|
Gains gains = new Gains();
|
|
gains.setCourse(course);
|
|
gains.setMasseAPartager(masseAPartager);
|
|
gains.setDateCalcul(LocalDateTime.now());
|
|
|
|
// Calcul détaillé des gains selon les règles
|
|
if (resultat.isADeadHeat()) {
|
|
calculerRapportsDeadHeat(gains, combinaisonsPayables, tousParis);
|
|
} else {
|
|
calculerRapportsNormaux(gains, combinaisonsPayables, tousParis);
|
|
}
|
|
|
|
// Gérer la cagnotte si nécessaire (Article 8 et 9)
|
|
gererCagnotte(gains, combinaisonsPayables);
|
|
|
|
return gainsRepository.save(gains);
|
|
}
|
|
|
|
private double calculerRemboursements(List<PariJumelePlace> paris) {
|
|
double remboursements = 0.0;
|
|
|
|
for (PariJumelePlace pari : paris) {
|
|
// Article 4: Remboursement si un ou deux chevaux non partants
|
|
if (pari.getCheval1().isEstNonPartant() || pari.getCheval2().isEstNonPartant()) {
|
|
remboursements += pari.getMise();
|
|
pari.setEstRembourse(true);
|
|
pariRepository.save(pari);
|
|
}
|
|
}
|
|
|
|
return remboursements;
|
|
}
|
|
|
|
private List<CombinaisonPayable> determinerCombinaisonsPayables(ResultatCourse resultat) {
|
|
List<CombinaisonPayable> combinaisons = new ArrayList<>();
|
|
|
|
// Article 1: Le pari est payable si les deux chevaux occupent deux des trois premières places
|
|
// Article 3: Gestion des dead-heats
|
|
|
|
if (resultat.isADeadHeat()) {
|
|
// Cas de dead-heat
|
|
if (resultat.getChevauxPremiers().size() >= 3) {
|
|
// Article 3a: Dead-heat de trois chevaux ou plus à la première place
|
|
// Toutes les combinaisons deux à deux des chevaux classés premiers
|
|
for (int i = 0; i < resultat.getChevauxPremiers().size(); i++) {
|
|
for (int j = i + 1; j < resultat.getChevauxPremiers().size(); j++) {
|
|
combinaisons.add(new CombinaisonPayable(
|
|
resultat.getChevauxPremiers().get(i),
|
|
resultat.getChevauxPremiers().get(j)
|
|
));
|
|
}
|
|
}
|
|
} else if (resultat.getChevauxPremiers().size() == 2 &&
|
|
!resultat.getChevauxTroisiemes().isEmpty()) {
|
|
// Article 3b: Dead-heat de deux chevaux à la première place et un ou plusieurs à la troisième
|
|
// Combinaison des deux premiers + combinaisons de chaque premier avec chaque troisième
|
|
combinaisons.add(new CombinaisonPayable(
|
|
resultat.getChevauxPremiers().get(0),
|
|
resultat.getChevauxPremiers().get(1)
|
|
));
|
|
|
|
for (Long chevalPremier : resultat.getChevauxPremiers()) {
|
|
for (Long chevalTroisieme : resultat.getChevauxTroisiemes()) {
|
|
combinaisons.add(new CombinaisonPayable(chevalPremier, chevalTroisieme));
|
|
}
|
|
}
|
|
} else if (resultat.getChevauxTroisiemes().size() >= 2) {
|
|
// Article 3c: Dead-heat de deux chevaux ou plus à la troisième place
|
|
// Combinaison premier + deuxième + combinaisons premier avec chaque troisième + deuxième avec chaque troisième
|
|
if (!resultat.getChevauxPremiers().isEmpty() && !resultat.getChevauxDeuxiemes().isEmpty()) {
|
|
combinaisons.add(new CombinaisonPayable(
|
|
resultat.getChevauxPremiers().get(0),
|
|
resultat.getChevauxDeuxiemes().get(0)
|
|
));
|
|
|
|
for (Long chevalTroisieme : resultat.getChevauxTroisiemes()) {
|
|
combinaisons.add(new CombinaisonPayable(
|
|
resultat.getChevauxPremiers().get(0),
|
|
chevalTroisieme
|
|
));
|
|
|
|
combinaisons.add(new CombinaisonPayable(
|
|
resultat.getChevauxDeuxiemes().get(0),
|
|
chevalTroisieme
|
|
));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Cas normal - Article 1: Les deux chevaux doivent occuper deux des trois premières places
|
|
List<Long> chevauxPayables = new ArrayList<>();
|
|
chevauxPayables.addAll(resultat.getChevauxPremiers());
|
|
chevauxPayables.addAll(resultat.getChevauxDeuxiemes());
|
|
chevauxPayables.addAll(resultat.getChevauxTroisiemes());
|
|
|
|
// Générer toutes les combinaisons deux à deux
|
|
for (int i = 0; i < chevauxPayables.size(); i++) {
|
|
for (int j = i + 1; j < chevauxPayables.size(); j++) {
|
|
combinaisons.add(new CombinaisonPayable(
|
|
chevauxPayables.get(i),
|
|
chevauxPayables.get(j)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
return combinaisons;
|
|
}
|
|
|
|
private void calculerRapportsNormaux(Gains gains, List<CombinaisonPayable> combinaisonsPayables,
|
|
List<PariJumelePlace> tousParis) {
|
|
// Article 5a: Cas d'arrivée normale
|
|
if (combinaisonsPayables.isEmpty()) {
|
|
gains.setMontantCagnotte(gains.getMasseAPartager());
|
|
gains.setMasseAPartager(0.0);
|
|
return;
|
|
}
|
|
|
|
// Article 5a: Le bénéfice à répartir est divisé en trois parties égales
|
|
double beneficeARepartir = gains.getMasseAPartager();
|
|
double part = beneficeARepartir / 3;
|
|
|
|
List<Double> rapports = new ArrayList<>();
|
|
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
double totalMises = calculerMisesSurCombinaison(combinaison, tousParis);
|
|
|
|
if (totalMises > 0) {
|
|
double rapport = (part / totalMises) + 1;
|
|
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
|
} else {
|
|
rapports.add(0.0);
|
|
}
|
|
}
|
|
|
|
gains.setRapports(rapports);
|
|
}
|
|
|
|
private void calculerRapportsDeadHeat(Gains gains, List<CombinaisonPayable> combinaisonsPayables,
|
|
List<PariJumelePlace> tousParis) {
|
|
// Article 5b: Cas d'arrivée "dead heat"
|
|
if (combinaisonsPayables.isEmpty()) {
|
|
gains.setMontantCagnotte(gains.getMasseAPartager());
|
|
gains.setMasseAPartager(0.0);
|
|
return;
|
|
}
|
|
|
|
double beneficeARepartir = gains.getMasseAPartager();
|
|
List<Double> rapports = new ArrayList<>();
|
|
|
|
// Déterminer le type de dead-heat pour appliquer les règles spécifiques
|
|
ResultatCourse resultat = gains.getCourse().getResultat();
|
|
|
|
if (resultat.getChevauxPremiers().size() >= 3) {
|
|
// Article 5b1: Dead-heat de trois chevaux ou plus à la première place
|
|
double partParCombinaison = beneficeARepartir / combinaisonsPayables.size();
|
|
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
double totalMises = calculerMisesSurCombinaison(combinaison, tousParis);
|
|
|
|
if (totalMises > 0) {
|
|
double rapport = (partParCombinaison / totalMises) + 1;
|
|
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
|
} else {
|
|
rapports.add(0.0);
|
|
}
|
|
}
|
|
} else if (resultat.getChevauxPremiers().size() == 2 &&
|
|
!resultat.getChevauxTroisiemes().isEmpty()) {
|
|
// Article 5b2: Dead-heat de deux chevaux à la première place et un ou plusieurs à la troisième
|
|
double part = beneficeARepartir / 3;
|
|
|
|
// Trouver la combinaison des deux premiers
|
|
CombinaisonPayable combinaisonPremiers = null;
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) &&
|
|
resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) {
|
|
combinaisonPremiers = combinaison;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Calculer les rapports
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
double totalMises = calculerMisesSurCombinaison(combinaison, tousParis);
|
|
|
|
if (totalMises > 0) {
|
|
double rapportPart = part;
|
|
if (combinaison.equals(combinaisonPremiers)) {
|
|
// Combinaison des deux premiers - 1/3 du bénéfice
|
|
rapportPart = part;
|
|
} else {
|
|
// Combinaisons premier-troisième - 1/3 du bénéfice divisé par le nombre de ces combinaisons
|
|
int nbCombinaisonsPremierTroisieme = combinaisonsPayables.size() - 1;
|
|
rapportPart = part / nbCombinaisonsPremierTroisieme;
|
|
}
|
|
|
|
double rapport = (rapportPart / totalMises) + 1;
|
|
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
|
} else {
|
|
rapports.add(0.0);
|
|
}
|
|
}
|
|
} else if (resultat.getChevauxDeuxiemes().size() >= 2) {
|
|
// Article 5b3: Dead-heat de deux chevaux ou plus à la deuxième place
|
|
double deuxTiers = beneficeARepartir * 2 / 3;
|
|
double unTiers = beneficeARepartir / 3;
|
|
|
|
// Compter les types de combinaisons
|
|
int nbCombinaisonsPremierDeuxieme = 0;
|
|
int nbCombinaisonsDeuxiemeDeuxieme = 0;
|
|
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) ||
|
|
resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) {
|
|
nbCombinaisonsPremierDeuxieme++;
|
|
} else {
|
|
nbCombinaisonsDeuxiemeDeuxieme++;
|
|
}
|
|
}
|
|
|
|
// Calculer les rapports
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
double totalMises = calculerMisesSurCombinaison(combinaison, tousParis);
|
|
|
|
if (totalMises > 0) {
|
|
double rapportPart;
|
|
if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) ||
|
|
resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) {
|
|
// Combinaisons premier-deuxième - 2/3 du bénéfice divisé par le nombre de ces combinaisons
|
|
rapportPart = deuxTiers / nbCombinaisonsPremierDeuxieme;
|
|
} else {
|
|
// Combinaisons deuxième-deuxième - 1/3 du bénéfice divisé par le nombre de ces combinaisons
|
|
rapportPart = unTiers / nbCombinaisonsDeuxiemeDeuxieme;
|
|
}
|
|
|
|
double rapport = (rapportPart / totalMises) + 1;
|
|
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
|
} else {
|
|
rapports.add(0.0);
|
|
}
|
|
}
|
|
} else if (resultat.getChevauxTroisiemes().size() >= 2) {
|
|
// Article 5b4: Dead-heat de deux chevaux ou plus à la troisième place
|
|
double part = beneficeARepartir / 3;
|
|
|
|
// Compter les types de combinaisons
|
|
int nbCombinaisonsPremierTroisieme = 0;
|
|
int nbCombinaisonsDeuxiemeTroisieme = 0;
|
|
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) ||
|
|
resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) {
|
|
nbCombinaisonsPremierTroisieme++;
|
|
} else if (resultat.getChevauxDeuxiemes().contains(combinaison.getCheval1Id()) ||
|
|
resultat.getChevauxDeuxiemes().contains(combinaison.getCheval2Id())) {
|
|
nbCombinaisonsDeuxiemeTroisieme++;
|
|
}
|
|
}
|
|
|
|
// Calculer les rapports
|
|
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
|
double totalMises = calculerMisesSurCombinaison(combinaison, tousParis);
|
|
|
|
if (totalMises > 0) {
|
|
double rapportPart;
|
|
if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) &&
|
|
resultat.getChevauxDeuxiemes().contains(combinaison.getCheval2Id()) ||
|
|
resultat.getChevauxPremiers().contains(combinaison.getCheval2Id()) &&
|
|
resultat.getChevauxDeuxiemes().contains(combinaison.getCheval1Id())) {
|
|
// Combinaison premier-deuxième - 1/3 du bénéfice
|
|
rapportPart = part;
|
|
} else if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) ||
|
|
resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) {
|
|
// Combinaisons premier-troisième - 1/3 du bénéfice divisé par le nombre de ces combinaisons
|
|
rapportPart = part / nbCombinaisonsPremierTroisieme;
|
|
} else {
|
|
// Combinaisons deuxième-troisième - 1/3 du bénéfice divisé par le nombre de ces combinaisons
|
|
rapportPart = part / nbCombinaisonsDeuxiemeTroisieme;
|
|
}
|
|
|
|
double rapport = (rapportPart / totalMises) + 1;
|
|
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
|
} else {
|
|
rapports.add(0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
gains.setRapports(rapports);
|
|
}
|
|
|
|
private double calculerMisesSurCombinaison(CombinaisonPayable combinaison, List<PariJumelePlace> tousParis) {
|
|
double totalMises = 0.0;
|
|
|
|
for (PariJumelePlace pari : tousParis) {
|
|
if ((pari.getCheval1().getId().equals(combinaison.getCheval1Id()) &&
|
|
pari.getCheval2().getId().equals(combinaison.getCheval2Id())) ||
|
|
(pari.getCheval1().getId().equals(combinaison.getCheval2Id()) &&
|
|
pari.getCheval2().getId().equals(combinaison.getCheval1Id()))) {
|
|
totalMises += pari.getMise();
|
|
}
|
|
}
|
|
|
|
return totalMises;
|
|
}
|
|
|
|
private void gererCagnotte(Gains gains, List<CombinaisonPayable> combinaisonsPayables) {
|
|
// Article 8 et 9: Gestion de la cagnotte
|
|
if (combinaisonsPayables.isEmpty()) {
|
|
gains.setMontantCagnotte(gains.getMasseAPartager());
|
|
gains.setMasseAPartager(0.0);
|
|
}
|
|
|
|
// Article 8d: Course annulée ou reportée
|
|
if (gains.getCourse().isEstAnnulee()) {
|
|
gains.setMontantCagnotte(gains.getMasseAPartager());
|
|
gains.setMasseAPartager(0.0);
|
|
}
|
|
}
|
|
|
|
// Classe interne pour représenter une combinaison payable
|
|
private static class CombinaisonPayable {
|
|
private Long cheval1Id;
|
|
private Long cheval2Id;
|
|
|
|
public CombinaisonPayable(Long cheval1Id, Long cheval2Id) {
|
|
this.cheval1Id = cheval1Id;
|
|
this.cheval2Id = cheval2Id;
|
|
}
|
|
|
|
public Long getCheval1Id() { return cheval1Id; }
|
|
public Long getCheval2Id() { return cheval2Id; }
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
CombinaisonPayable that = (CombinaisonPayable) o;
|
|
return (Objects.equals(cheval1Id, that.cheval1Id) &&
|
|
Objects.equals(cheval2Id, that.cheval2Id)) ||
|
|
(Objects.equals(cheval1Id, that.cheval2Id) &&
|
|
Objects.equals(cheval2Id, that.cheval1Id));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
long id1 = Math.min(cheval1Id, cheval2Id);
|
|
long id2 = Math.max(cheval1Id, cheval2Id);
|
|
return Objects.hash(id1, id2);
|
|
}
|
|
}
|
|
|
|
public Gains obtenirGainsParCourse(Long courseId) {
|
|
return gainsRepository.findByCourseId(courseId);
|
|
}
|
|
}
|