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 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 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 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 calculerPaiements(RequeteResultatMulti requete) { ResultatCourseMulti resultat = creerResultat(requete); List paris = pariRepository.findByCourseId(requete.getCourseId()); List 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> parisGagnantsParType = new HashMap<>(); Map 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 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 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 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 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 paiements, Double masseAPartager, Map 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 paiements, TypeMulti typeMulti) { TypePaiementMulti typePaiement = getTypePaiementFromTypeMulti(typeMulti); List 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 paiements, Double masseAPartager, Map 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 paiements, Double masseAPartager, Map 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 paiements, Double masseAPartager, Map 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 paiements, Double masseAPartager, Map 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 gererDeadHeat(ResultatCourseMulti resultat, TypeDeadHeat typeDeadHeat) { List paris = pariRepository.findByCourseId(resultat.getCourse().getId()); List 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 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 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 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 gererCasParticuliers(RequeteResultatMulti requete) { ResultatCourseMulti resultat = creerResultat(requete); List 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 paris, ResultatCourseMulti resultat) { for (PariMulti pari : paris) { if (estCombinaisonGagnante(pari.getChevauxSelectionnes(), resultat)) { return true; } } return false; } private List appliquerReglesDegradation(ResultatCourseMulti resultat, List paris) { // Article 9: Application des règles de dégradation List 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 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 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 getStatistiquesParis(Long courseId) { List paris = pariRepository.findByCourseId(courseId); return paris.stream() .collect(Collectors.groupingBy(PariMulti::getTypeMulti, Collectors.summingInt(p -> 1))); } public Map getStatistiquesFormules(Long courseId) { List 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 getCagnottesDisponibles() { return cagnotteRepository.findByUtilisee(false); } public List getHistoriquePaiementsParieur(Long parieurId) { List paris = pariRepository.findByParieurId(parieurId); return paris.stream() .flatMap(pari -> paiementRepository.findByPariId(pari.getId()).stream()) .collect(Collectors.toList()); } }