Fin intégration quinte plus

This commit is contained in:
sidibe
2025-08-26 17:55:28 +00:00
parent 69b0fad8e8
commit dc771d8cd8
217 changed files with 4175 additions and 1224 deletions

View File

@@ -1,40 +0,0 @@
package com.pmu.jumele.controller;
import com.pmu.jumele.dto.*;
import com.pmu.jumele.service.JumeleGagnantService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/jumele")
public class JumeleController {
private final JumeleGagnantService service;
public JumeleController(JumeleGagnantService service) {
this.service = service;
}
@PostMapping("/pari")
public ResponseEntity<?> enregistrerPari(@RequestBody PariRequest req) {
try {
Map<String, Object> res = service.enregistrerPari(req);
return ResponseEntity.ok(res);
} catch (IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage()));
}
}
@PostMapping("/resultat")
public ResponseEntity<?> calculer(@RequestBody PositionsRequest req) {
Map<String, Object> res = service.calculer(req);
return ResponseEntity.ok(res);
}
@GetMapping("/paris/{courseId}")
public ResponseEntity<?> listerParis(@PathVariable String courseId) {
return ResponseEntity.ok(service.getClass().getName()); // placeholder: add endpoint to fetch from repo if needed
}
}

View File

@@ -1,14 +0,0 @@
package com.pmu.jumele.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
public class PaiementResponse {
private String parieur;
private boolean gagnant;
private BigDecimal gain;
private String combinaison;
}

View File

@@ -1,14 +0,0 @@
package com.pmu.jumele.dto;
import lombok.Data;
import java.util.List;
@Data
public class PariRequest {
private String parieur;
private String courseId;
private List<Integer> chevaux; // can be 2 (unit), or list to generate combinations (combiné/champ)
private int mise; // montant total for the formula (we'll split for unitary bets)
private String formuleType; // "unitaire","combine","champ_total","champ_partiel"
private List<Integer> champSelection; // used for champ partiel (if formuleType == champ_partiel)
}

View File

@@ -1,14 +0,0 @@
package com.pmu.jumele.dto;
import lombok.Data;
import java.util.List;
@Data
public class PositionsRequest {
private String courseId;
// positions: list of positions; each position is a list of horse numbers (to represent dead-heat)
// positions.get(0) = list of horses classified first
// positions.get(1) = list of horses classified second, etc.
private List<List<Integer>> positions;
private List<Integer> nonPartants; // optional
}

View File

@@ -1,24 +0,0 @@
package com.pmu.jumele.entity;
import jakarta.persistence.*;
import lombok.*;
import java.math.BigDecimal;
@Entity
@Table(name = "paris_jumele")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PariEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String parieur;
private String courseId;
// store combinaison as "a,b"
private String combinaison;
private BigDecimal mise;
}

View File

@@ -1,10 +0,0 @@
package com.pmu.jumele.repository;
import com.pmu.jumele.entity.PariEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PariRepository extends JpaRepository<PariEntity, Long> {
List<PariEntity> findByCourseId(String courseId);
List<PariEntity> findByCourseIdAndParieurAndCombinaison(String courseId, String parieur, String combinaison);
}

View File

@@ -1,290 +0,0 @@
package com.pmu.jumele.service;
import com.pmu.jumele.dto.*;
import com.pmu.jumele.entity.PariEntity;
import com.pmu.jumele.repository.PariRepository;
import com.pmu.jumele.util.CombinaisonUtil;
import com.pmu.jumele.util.FormulesTable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class JumeleGagnantService {
private final PariRepository pariRepository;
private static final BigDecimal MISE_MIN = BigDecimal.valueOf(500);
private static final int PLAFOND_MULT = 20;
private static final BigDecimal RAPPORT_MIN = BigDecimal.valueOf(1.1);
private static final int SCALE = 8;
// prélèvements légaux (paramétrable)
private BigDecimal prelevements = BigDecimal.ZERO;
public JumeleGagnantService(PariRepository pariRepository) {
this.pariRepository = pariRepository;
}
public void setPrelevements(BigDecimal p) { this.prelevements = p == null ? BigDecimal.ZERO : p; }
/**
* Enregistre un pari request — peut être unitaire ou une formule.
* Pour formules, convertit en paris unitaires (combinaisons) et enregistre chaque unité en respectant le tableau de valeurs.
*/
@Transactional
public Map<String, Object> enregistrerPari(PariRequest req) {
Map<String, Object> result = new HashMap<>();
// validation minimale
if (req.getMise() < MISE_MIN.intValue()) throw new IllegalArgumentException("Mise minimum = 500");
// calculer unités
Set<Set<Integer>> combsToPlace = new HashSet<>();
if ("unitaire".equalsIgnoreCase(req.getFormuleType()) || req.getChevaux().size() == 2) {
if (req.getChevaux().size() != 2) throw new IllegalArgumentException("Pari unitaire doit contenir 2 chevaux");
combsToPlace.add(new HashSet<>(req.getChevaux()));
} else if ("combine".equalsIgnoreCase(req.getFormuleType())) {
combsToPlace = CombinaisonUtil.allPairs(req.getChevaux());
} else if ("champ_total".equalsIgnoreCase(req.getFormuleType())) {
// champ total: base x all others; req.getChevaux() must contain base + others?
// For API simplicity: req.chevaux contains full list of partants; champ_total of a base is handled client-side by sending combos
combsToPlace = CombinaisonUtil.allPairs(req.getChevaux());
} else if ("champ_partiel".equalsIgnoreCase(req.getFormuleType())) {
if (req.getChampSelection() == null || req.getChampSelection().isEmpty())
throw new IllegalArgumentException("Champ partiel nécessite champSelection");
// base is first element of req.chevaux
Integer base = req.getChevaux().get(0);
for (Integer c : req.getChampSelection()) {
combsToPlace.add(new HashSet<>(Arrays.asList(base, c)));
}
} else {
throw new IllegalArgumentException("FormuleType inconnu");
}
// valeur unitaire par combinaison : on divise la mise totale proportionnellement selon tableau ou uniformément
// Simplicité : on divise la mise totale uniformément par le nombre de combinaisons
int nbComb = combsToPlace.size();
BigDecimal totalMise = BigDecimal.valueOf(req.getMise());
BigDecimal miseUnitaire = totalMise.divide(BigDecimal.valueOf(nbComb), SCALE, RoundingMode.HALF_UP);
// enregistrement avec respect du plafond 20 prises par parieur sur même combinaison
BigDecimal plafond = MISE_MIN.multiply(BigDecimal.valueOf(PLAFOND_MULT));
BigDecimal totalRembourse = BigDecimal.ZERO;
List<PariEntity> enregistrés = new ArrayList<>();
for (Set<Integer> comb : combsToPlace) {
String combStr = CombinaisonUtil.combToString(comb);
// somme déjà engagée par ce parieur sur cette combinaison dans la même course
List<PariEntity> deja = pariRepository.findByCourseIdAndParieurAndCombinaison(req.getCourseId(), req.getParieur(), combStr);
BigDecimal dejaTotal = deja.stream().map(PariEntity::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal disponible = plafond.subtract(dejaTotal);
BigDecimal toRecord = miseUnitaire.min(disponible);
BigDecimal toRefund = miseUnitaire.subtract(toRecord);
if (toRecord.compareTo(BigDecimal.ZERO) > 0) {
PariEntity e = PariEntity.builder()
.parieur(req.getParieur())
.courseId(req.getCourseId())
.combinaison(combStr)
.mise(toRecord)
.build();
pariRepository.save(e);
enregistrés.add(e);
}
if (toRefund.compareTo(BigDecimal.ZERO) > 0) {
totalRembourse = totalRembourse.add(toRefund);
}
}
result.put("enregistres", enregistrés.size());
result.put("a_rembourser", totalRembourse.setScale(2, RoundingMode.HALF_UP));
return result;
}
/**
* Enregistre le résultat via PositionsRequest (positions = list of lists to express dead-heats).
*/
public void enregistrerResultat(PositionsRequest req) {
// store positions in memory — for calculation we require positions per course; for simplicity, pass positions during calcul.
// In this implementation, we'll pass PositionsRequest directly to calculer.
// To persist results, create a ResultEntity (omitted here).
}
/**
* Calculer paiements pour une course en fournissant PositionsRequest (dead-heat possible).
*/
@Transactional(readOnly = true)
public Map<String, Object> calculer(PositionsRequest positionsReq) {
Map<String, Object> out = new HashMap<>();
String courseId = positionsReq.getCourseId();
List<PariEntity> parisCourse = pariRepository.findByCourseId(courseId);
List<Integer> nonPartants = positionsReq.getNonPartants() == null ? Collections.emptyList() : positionsReq.getNonPartants();
// 1) Générer combinaisons payables selon article 3:
// positionsReq.positions[0] => premiers (list), positionsReq.positions[1] => deuxiemes (list), etc.
List<List<Integer>> positions = positionsReq.getPositions();
if (positions == null || positions.size() < 2) {
// moins de deux classés => tout remboursé (Article 8)
List<PaiementResponse> remboursements = parisCourse.stream()
.map(p -> new PaiementResponse(p.getParieur(), false, p.getMise(), p.getCombinaison()))
.collect(Collectors.toList());
out.put("paiements", remboursements);
out.put("cagnotte", BigDecimal.ZERO.setScale(2));
return out;
}
Set<Set<Integer>> combPayables = new HashSet<>();
// a) dead-heat premiers (positions[0] size >=2) -> toutes combinaisons 2-à-2 parmi premiers
List<Integer> premiers = positions.get(0);
if (premiers.size() >= 2) {
combPayables.addAll(CombinaisonUtil.allPairs(premiers));
}
// b) dead-heat seconds (positions[1] size >=2) -> all pairs combining each premier with each second
List<Integer> deuxiemes = positions.get(1);
if (deuxiemes.size() >= 1 && premiers.size() >= 1) {
for (Integer p : premiers) {
for (Integer d : deuxiemes) {
combPayables.add(new HashSet<>(Arrays.asList(p, d)));
}
}
}
// c) if positions[0].size()==1 and positions[1].size()==1 and no dead-heat: comb = {1er,2e}
if (premiers.size() == 1 && deuxiemes.size() == 1) {
combPayables.add(new HashSet<>(Arrays.asList(premiers.get(0), deuxiemes.get(0))));
}
// Remove combos involving non-partants -> these combos are remboursed (Article 4)
Set<Set<Integer>> combRemb = combPayables.stream()
.filter(c -> c.stream().anyMatch(nonPartants::contains))
.collect(Collectors.toSet());
// RNET = total des mises enregistrées sur la course
BigDecimal rnet = parisCourse.stream().map(PariEntity::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
// MREMB = montant des mises sur combRemb
BigDecimal mremb = parisCourse.stream()
.filter(p -> combRemb.contains(stringToComb(p.getCombinaison())))
.map(PariEntity::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal map = rnet.subtract(mremb).subtract(prelevements);
if (map.compareTo(BigDecimal.ZERO) < 0) map = BigDecimal.ZERO;
// Mises par combinaison (pour combPayables not remboursed)
Map<Set<Integer>, BigDecimal> misesParComb = new HashMap<>();
for (Set<Integer> comb : combPayables) {
if (combRemb.contains(comb)) {
misesParComb.put(comb, BigDecimal.ZERO);
} else {
BigDecimal s = parisCourse.stream()
.filter(p -> stringToComb(p.getCombinaison()).equals(comb))
.map(PariEntity::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
misesParComb.put(comb, s);
}
}
// cas : aucune comb active => cagnotte
List<Set<Integer>> combActives = misesParComb.entrySet().stream()
.filter(e -> e.getValue().compareTo(BigDecimal.ZERO) > 0)
.map(Map.Entry::getKey).collect(Collectors.toList());
BigDecimal cagnotte = BigDecimal.ZERO;
Map<String, BigDecimal> gains = new HashMap<>();
if (combActives.isEmpty()) {
cagnotte = cagnotte.add(map);
} else if (combActives.size() == 1) {
// cas normal unique combinaison
Set<Integer> comb = combActives.get(0);
BigDecimal totMise = misesParComb.get(comb);
if (totMise.compareTo(BigDecimal.ZERO) == 0) {
cagnotte = cagnotte.add(map);
} else {
BigDecimal rapport = map.divide(totMise, SCALE, RoundingMode.HALF_UP).add(BigDecimal.ONE);
if (rapport.compareTo(RAPPORT_MIN) < 0) rapport = RAPPORT_MIN;
// payer chaque parieur sur la combinaison
for (PariEntity p : parisCourse) {
if (stringToComb(p.getCombinaison()).equals(comb)) {
BigDecimal gain = p.getMise().multiply(rapport).setScale(2, RoundingMode.HALF_UP);
gains.put(p.getParieur(), gains.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(gain));
}
}
}
} else {
// dead-heat / multiple combs payables
BigDecimal totalMisesOnCombActives = combActives.stream().map(misesParComb::get).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal benef = map.subtract(totalMisesOnCombActives);
if (benef.compareTo(BigDecimal.ZERO) < 0) benef = BigDecimal.ZERO;
int nbComb = combActives.size();
BigDecimal partParComb = benef.divide(BigDecimal.valueOf(nbComb), SCALE, RoundingMode.HALF_UP);
// allocate for each comb: partParComb distributed proportionally to mises on that comb
// but first for each comb we already have misesParComb.get(comb) that were removed from the map when computing benef
for (Set<Integer> comb : combActives) {
BigDecimal misesThis = misesParComb.get(comb);
if (misesThis.compareTo(BigDecimal.ZERO) == 0) continue;
BigDecimal ratio = partParComb.divide(misesThis, SCALE, RoundingMode.HALF_UP);
BigDecimal rapport = ratio.add(BigDecimal.ONE);
if (rapport.compareTo(RAPPORT_MIN) < 0) rapport = RAPPORT_MIN;
for (PariEntity p : parisCourse) {
if (stringToComb(p.getCombinaison()).equals(comb)) {
BigDecimal gain = p.getMise().multiply(rapport).setScale(2, RoundingMode.HALF_UP);
gains.put(p.getParieur(), gains.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(gain));
}
}
}
// traiter parts non couvertes (combActives avec mises 0): redistribuer their part among covered combs
BigDecimal partsNonCover = BigDecimal.ZERO;
for (Set<Integer> comb : combActives) {
if (misesParComb.get(comb).compareTo(BigDecimal.ZERO) == 0) {
partsNonCover = partsNonCover.add(partParComb);
}
}
if (partsNonCover.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal totalMisesCouvertes = combActives.stream()
.filter(c -> misesParComb.get(c).compareTo(BigDecimal.ZERO) > 0)
.map(misesParComb::get).reduce(BigDecimal.ZERO, BigDecimal::add);
if (totalMisesCouvertes.compareTo(BigDecimal.ZERO) == 0) {
cagnotte = cagnotte.add(map);
} else {
// redistribute partsNonCover to covered combos proportionally
for (Set<Integer> comb : combActives) {
BigDecimal misesThis = misesParComb.get(comb);
if (misesThis.compareTo(BigDecimal.ZERO) == 0) continue;
BigDecimal share = partsNonCover.multiply(misesThis).divide(totalMisesCouvertes, SCALE, RoundingMode.HALF_UP);
// distribute share proportionally to parieurs on the comb
for (PariEntity p : parisCourse) {
if (stringToComb(p.getCombinaison()).equals(comb)) {
BigDecimal extra = p.getMise().multiply(share).divide(misesThis, SCALE, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
gains.put(p.getParieur(), gains.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(extra));
}
}
}
}
}
}
// Build payment responses per record (each pari)
List<PaiementResponse> payments = new ArrayList<>();
for (PariEntity p : parisCourse) {
BigDecimal gain = gains.getOrDefault(p.getParieur(), BigDecimal.ZERO);
boolean gagnant = gain.compareTo(BigDecimal.ZERO) > 0;
payments.add(new PaiementResponse(p.getParieur(), gagnant, gain, p.getCombinaison()));
}
out.put("paiements", payments);
out.put("cagnotte", cagnotte.setScale(2, RoundingMode.HALF_UP));
out.put("rnet", rnet.setScale(2, RoundingMode.HALF_UP));
out.put("mremb", mremb.setScale(2, RoundingMode.HALF_UP));
out.put("map", map.setScale(2, RoundingMode.HALF_UP));
return out;
}
private Set<Integer> stringToComb(String s) {
return Arrays.stream(s.split(",")).map(Integer::valueOf).collect(Collectors.toSet());
}
}

View File

@@ -1,47 +0,0 @@
package com.pmu.jumele.service;
import com.pmu.jumele.dto.PariRequest;
import com.pmu.jumele.dto.PositionsRequest;
import com.pmu.jumele.entity.PariEntity;
import com.pmu.jumele.repository.PariRepository;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
//@DataJpaTest
public class JumeleGagnantServiceTest {
// @Autowired
PariRepository repo;
// @Test
void test_simple_flow() {
JumeleGagnantService svc = new JumeleGagnantService(repo);
PariRequest pr = new PariRequest();
pr.setParieur("Alice");
pr.setCourseId("C1");
pr.setChevaux(Arrays.asList(1,2));
pr.setMise(500);
pr.setFormuleType("unitaire");
svc.enregistrerPari(pr);
// ajouter un autre pari sur la même comb
pr.setParieur("Bob");
svc.enregistrerPari(pr);
PositionsRequest pos = new PositionsRequest();
pos.setCourseId("C1");
pos.setPositions(List.of(List.of(1), List.of(2))); // 1er=1 ; 2e=2
Map<String,Object> res = svc.calculer(pos);
// assertThat(res).containsKey("paiements");
}
}

View File

@@ -1,24 +0,0 @@
package com.pmu.jumele.util;
import java.util.*;
import java.util.stream.Collectors;
public class CombinaisonUtil {
// génère toutes paires (unordered) d'une liste de chevaux
public static Set<Set<Integer>> allPairs(List<Integer> chevaux) {
Set<Set<Integer>> res = new HashSet<>();
int n = chevaux.size();
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
res.add(new HashSet<>(Arrays.asList(chevaux.get(i), chevaux.get(j))));
}
}
return res;
}
// conversion Set<Integer> -> "a,b" sorted string
public static String combToString(Set<Integer> comb) {
return comb.stream().sorted().map(Object::toString).collect(Collectors.joining(","));
}
}

View File

@@ -1,35 +0,0 @@
package com.pmu.jumele.util;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
public class FormulesTable {
// For simplicity we store unit value per formula (complete combined) and simplified etc.
// We'll only need unit price to split a given "mise" into each unit bet when user submits a formula.
// Here we give reference table values per article 7 (unit values for "complete" formulas)
public static final Map<Integer, BigDecimal> COMBINED_COMPLETE = new HashMap<>();
public static final Map<Integer, BigDecimal> COMBINED_SIMPLE = new HashMap<>();
public static final Map<Integer, BigDecimal> CHAMP_TOTAL = new HashMap<>();
public static final Map<Integer, BigDecimal> CHAMP_PARTIEL = new HashMap<>();
static {
// fill a subset (full table can be added)
COMBINED_COMPLETE.put(3, BigDecimal.valueOf(3000));
COMBINED_COMPLETE.put(4, BigDecimal.valueOf(6000));
COMBINED_COMPLETE.put(5, BigDecimal.valueOf(10000));
// ... add rest as needed
COMBINED_SIMPLE.put(3, BigDecimal.valueOf(1500));
COMBINED_SIMPLE.put(4, BigDecimal.valueOf(3000));
// ...
CHAMP_TOTAL.put(3, BigDecimal.valueOf(1000));
CHAMP_TOTAL.put(4, BigDecimal.valueOf(1500));
// ...
CHAMP_PARTIEL.put(3, BigDecimal.valueOf(1500));
CHAMP_PARTIEL.put(4, BigDecimal.valueOf(2000));
// ...
}
}

View File

@@ -1,16 +0,0 @@
package com.pmu.mali.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
public class Pari {
private String parieur; // identifiant client ou ticket
private int numeroCheval; // numéro du cheval parié
private TypePari type; // SIMPLE_GAGNANT ou SIMPLE_PLACE
private BigDecimal mise; // montant mis (ex: 500)
private boolean nonPartant; // vrai si cheval non-partant (remboursement)
private String ecurieId; // identifiant d'écurie / coupling (null si aucun)
}

View File

@@ -1,16 +0,0 @@
package com.pmu.mali.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class ResultatCourse {
// listes des numéros de chevaux classés (peuvent contenir plusieurs en cas de dead-heat)
private List<Integer> premiers; // un ou plusieurs (dead-heat)
private List<Integer> deuxiemes; // peut être vide
private List<Integer> troisiemes; // peut être vide
private int nombrePartants; // nombre de chevaux engagés (programme officiel)
}

View File

@@ -1,6 +0,0 @@
package com.pmu.mali.model;
public enum TypePari {
SIMPLE_GAGNANT,
SIMPLE_PLACE
}

View File

@@ -1,367 +0,0 @@
package com.pmu.mali.service;
import com.pmu.mali.model.Pari;
import com.pmu.mali.model.ResultatCourse;
import com.pmu.mali.model.TypePari;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Service de calcul des gains pour PARI SIMPLE (gagnant / placé)
* Implémente écurie, dead-heat, remboursements, cagnotte, rapport min.
*/
@Service
public class CalculPariService {
private static final BigDecimal RAPPORT_MIN = BigDecimal.valueOf(1.1); // rapport minimum
private static final int SCALE = 6; // précision interne
/**
* Résultat retourné : map parieur -> gain (montant payé, inclut la mise).
* On retourne aussi une structure pour la cagnotte (optionnel).
*/
public static class ResultatCalcul {
public Map<String, BigDecimal> gainsParParieur = new HashMap<>();
public BigDecimal cagnotteGagnant = BigDecimal.ZERO;
public BigDecimal cagnottePlace = BigDecimal.ZERO;
}
/**
* Point d'entrée principal.
*
* @param paris liste de paris (SIMPLE_GAGNANT et SIMPLE_PLACE mélangés)
* @param resultat résultat de la course
* @param prelevements montant total prélevé légalement (sur les deux types, si applicable)
* @return ResultatCalcul
*/
public ResultatCalcul calculer(List<Pari> paris, ResultatCourse resultat, BigDecimal prelevements) {
ResultatCalcul res = new ResultatCalcul();
if (prelevements == null) prelevements = BigDecimal.ZERO;
// Séparer paris par type
List<Pari> parisGagnant = filterByType(paris, TypePari.SIMPLE_GAGNANT);
List<Pari> parisPlace = filterByType(paris, TypePari.SIMPLE_PLACE);
// 1) Remboursement des non-partants
BigDecimal rembGagnant = rembourserNonPartants(parisGagnant, res);
BigDecimal rembPlace = rembourserNonPartants(parisPlace, res);
// 2) Calcul des masses à partager (RNET - MREMB - PRELEV)
BigDecimal masseGagnant = totalMise(parisGagnant).subtract(rembGagnant).subtract(prelevements);
BigDecimal massePlace = totalMise(parisPlace).subtract(rembPlace).subtract(prelevements);
if (masseGagnant.compareTo(BigDecimal.ZERO) < 0) masseGagnant = BigDecimal.ZERO;
if (massePlace.compareTo(BigDecimal.ZERO) < 0) massePlace = BigDecimal.ZERO;
// 3) Calcul gagnant (avec ecurie et dead-heat)
calculerGagnant(parisGagnant, resultat, masseGagnant, res);
// 4) Calcul placé (avec règles 4-7 / >=8 et dead-heat)
calculerPlace(parisPlace, resultat, massePlace, res);
return res;
}
/* ----------------------
Méthodes utilitaires
---------------------- */
private List<Pari> filterByType(List<Pari> all, TypePari t) {
return all.stream().filter(p -> p.getType() == t).collect(Collectors.toList());
}
private BigDecimal totalMise(List<Pari> list) {
return list.stream().map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* Rembourse toutes les mises dont pari.nonPartant == true
* Ajoute le montant remboursé aux gains du parieur.
* Retourne le total remboursé.
*/
private BigDecimal rembourserNonPartants(List<Pari> list, ResultatCalcul res) {
return rembourserNonPartants(list, res.gainsParParieur);
}
private BigDecimal rembourserNonPartants(List<Pari> list, Map<String, BigDecimal> gainsMap) {
BigDecimal total = BigDecimal.ZERO;
for (Pari p : list) {
if (p.isNonPartant()) {
total = total.add(p.getMise());
gainsMap.put(p.getParieur(),
gainsMap.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(p.getMise()));
}
}
return total;
}
/* ---------------------------------------
Calcul du GAGNANT (inclut dead-heat/ecurie)
--------------------------------------- */
private void calculerGagnant(List<Pari> parisGagnant, ResultatCourse resultat, BigDecimal masse, ResultatCalcul res) {
List<Integer> premiers = resultat.getPremiers(); // liste des premiers (1 ou plusieurs)
if (premiers == null || premiers.isEmpty()) {
// Aucun cheval classé -> masse va en cagnotte
res.cagnotteGagnant = res.cagnotteGagnant.add(masse);
return;
}
// Si arrivée normale (un seul premier)
if (premiers.size() == 1) {
int premier = premiers.get(0);
// déterminer écurie du cheval gagnant (s'il y a), puis totaliser mises sur l'écurie
// On crée une clé d'ecurie : si pari.ecurieId == null, utilises le numéro du cheval comme "ecurie"
Map<String, BigDecimal> miseParEcurie = new HashMap<>();
for (Pari p : parisGagnant) {
if (p.isNonPartant()) continue;
String ecurie = keyEcurie(p);
// Si ce cheval appartient à l'écurie gagnante OU il est lui-même le gagnant, on prendra en compte
miseParEcurie.put(ecurie, miseParEcurie.getOrDefault(ecurie, BigDecimal.ZERO).add(p.getMise()));
}
String ecurieGagnanteKey = null;
// trouver si le gagnant a une écurie dans les paris
Optional<Pari> anyPariSurGagnant = parisGagnant.stream()
.filter(p -> p.getNumeroCheval() == premier && !p.isNonPartant()).findAny();
if (anyPariSurGagnant.isPresent()) {
ecurieGagnanteKey = keyEcurie(anyPariSurGagnant.get());
} else {
// pas de mise sur le gagnant -> vérifier s'il y a mises sur autres chevaux de la même écurie
// chercher toute mise sur chevaux avec même ecurieId (si ecurie known)
// Si pas de mise du tout sur la combinaison gagnante => cagnotte
// So we'll compute totalMisePayable below
}
// calculer total des mises payables (mises sur le cheval gagnant et, si écurie, sur ses co-écuries)
BigDecimal totalMisePayable = BigDecimal.ZERO;
if (ecurieGagnanteKey != null) {
totalMisePayable = miseParEcurie.getOrDefault(ecurieGagnanteKey, BigDecimal.ZERO);
} else {
// pas de pari identifié sur gagnant -> peut être 0
// cherché toutes mises portant exactement sur le cheval gagnant
totalMisePayable = parisGagnant.stream()
.filter(p -> p.getNumeroCheval() == premier && !p.isNonPartant())
.map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
}
if (totalMisePayable.compareTo(BigDecimal.ZERO) <= 0) {
// Aucun pari sur le gagnant (ni sur son ecurie) => vers cagnotte
res.cagnotteGagnant = res.cagnotteGagnant.add(masse);
return;
}
// Rapport brut = (masse / totalMisePayable) + 1 (on restitue la mise + bénéfice)
BigDecimal rapport = masse.divide(totalMisePayable, SCALE, RoundingMode.HALF_UP).add(BigDecimal.ONE);
if (rapport.compareTo(RAPPORT_MIN) < 0) rapport = RAPPORT_MIN;
// payer chaque parieur ayant parié sur ce cheval/ecurie
for (Pari p : parisGagnant) {
if (p.isNonPartant()) continue;
String key = keyEcurie(p);
boolean payable = false;
if (ecurieGagnanteKey != null) {
// payables si même ecurie
payable = ecurieGagnanteKey.equals(key);
} else {
payable = (p.getNumeroCheval() == premier);
}
if (payable) {
BigDecimal gain = p.getMise().multiply(rapport).setScale(2, RoundingMode.HALF_UP);
res.gainsParParieur.put(p.getParieur(),
res.gainsParParieur.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(gain));
}
}
return;
}
/* -------------------------
* Cas dead-heat (plusieurs premiers)
* ------------------------- */
// 1) calculer total mise sur les chevaux payables (les chevaux classés premiers)
// Règle du règlement : "le montant de toutes les mises sur les divers chevaux payables est dabord retiré de la masse à partager."
BigDecimal misesSurPayables = parisGagnant.stream()
.filter(p -> !p.isNonPartant() && premiers.contains(p.getNumeroCheval()))
.map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
// Bénéfice à répartir
BigDecimal benefice = masse.subtract(misesSurPayables);
if (benefice.compareTo(BigDecimal.ZERO) < 0) benefice = BigDecimal.ZERO;
int nbChevauxPremiers = premiers.size();
if (nbChevauxPremiers == 0) {
res.cagnotteGagnant = res.cagnotteGagnant.add(masse);
return;
}
// Diviser le bénéfice en autant de parts qu'il y a de chevaux classés premiers
BigDecimal partParCheval = benefice.divide(BigDecimal.valueOf(nbChevauxPremiers), SCALE, RoundingMode.HALF_UP);
// Pour chaque cheval premier : répartir sa part au prorata des mises sur ce cheval
for (Integer cheval : premiers) {
// mises sur ce cheval
BigDecimal misesSurCheval = parisGagnant.stream()
.filter(p -> !p.isNonPartant() && p.getNumeroCheval() == cheval)
.map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
if (misesSurCheval.compareTo(BigDecimal.ZERO) == 0) {
// si aucune mise sur ce cheval, sa part est redistribuée entre les autres premiers
// impl: ajouter cette part à cagnotte temporaire pour redistribution
// pour simplicité on ajoute à cagnotte et on répartira après
res.cagnotteGagnant = res.cagnotteGagnant.add(partParCheval);
continue;
}
// montant additionnel par unité de mise = partParCheval / misesSurCheval
BigDecimal ratio = partParCheval.divide(misesSurCheval, SCALE, RoundingMode.HALF_UP);
BigDecimal rapport = ratio.add(BigDecimal.ONE); // ajoute la mise
if (rapport.compareTo(RAPPORT_MIN) < 0) rapport = RAPPORT_MIN;
// payer chaque parieur sur ce cheval
for (Pari p : parisGagnant) {
if (p.isNonPartant()) continue;
if (p.getNumeroCheval() == cheval) {
BigDecimal gain = p.getMise().multiply(rapport).setScale(2, RoundingMode.HALF_UP);
res.gainsParParieur.put(p.getParieur(),
res.gainsParParieur.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(gain));
} else {
// si écurie: si cheval p appartient à écurie du cheval premier, il peut avoir droit (règle d'écurie)
// on doit totaliser mises par écurie gagnante et partager la part correspondante.
}
}
}
// NB: gestion complète des écuries dans dead-heat nécessite agrégation par ecurie + redistribution des parts relatives.
// Pour respecter à la lettre, on devrait :
// - agréger mises par ecurie pour les chevaux premiers,
// - distribuer la partParCheval à l'ecurie, puis parmi les chevaux de l'ecurie proportionnellement,
// - ci-dessus on a fait la distribution cheval-par-cheval (simplifié).
}
private String keyEcurie(Pari p) {
if (p.getEcurieId() != null && !p.getEcurieId().trim().isEmpty()) {
return "E:" + p.getEcurieId();
} else {
return "H:" + p.getNumeroCheval(); // seul cheval = sa "propre ecurie"
}
}
/* ---------------------------------------
* Calcul du PLACE (inclut dead-heat/ecurie)
* --------------------------------------- */
private void calculerPlace(List<Pari> parisPlace, ResultatCourse resultat, BigDecimal masse, ResultatCalcul res) {
// déterminer horses payable: si >=8 partants => 3 places payées, si 4-7 => 2, sinon mise en cagnotte (article)
int nPartants = resultat.getNombrePartants();
int nbPlacesPayees;
if (nPartants >= 8) nbPlacesPayees = 3;
else if (nPartants >= 4) nbPlacesPayees = 2;
else {
// si moins de 4 partants, la masse place va en cagnotte
res.cagnottePlace = res.cagnottePlace.add(masse);
return;
}
// Construction des chevaux payables selon les listes de résultat :
// Remarque : listes peuvent contenir dead-heats (plusieurs elements)
List<Integer> payables = new ArrayList<>();
if (nbPlacesPayees == 2) {
payables.addAll(resultat.getPremiers());
payables.addAll(resultat.getDeuxiemes());
} else {
// 3 places
payables.addAll(resultat.getPremiers());
payables.addAll(resultat.getDeuxiemes());
payables.addAll(resultat.getTroisiemes());
}
// on conserve distincts tout en gardant l'ordre logique
LinkedHashSet<Integer> set = new LinkedHashSet<>(payables);
List<Integer> chevauxPayables = new ArrayList<>(set);
if (chevauxPayables.isEmpty()) {
res.cagnottePlace = res.cagnottePlace.add(masse);
return;
}
// Retirer "le montant de toutes les mises sur les divers chevaux payables" de la masse
BigDecimal miseSurPayables = parisPlace.stream()
.filter(p -> !p.isNonPartant() && chevauxPayables.contains(p.getNumeroCheval()))
.map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal benefice = masse.subtract(miseSurPayables);
if (benefice.compareTo(BigDecimal.ZERO) < 0) benefice = BigDecimal.ZERO;
// Le bénéfice est divisé en autant de parts égales qu'il y a de "combinaisons payables".
// Ici on considère "combinaisons payables" = nombre de positions payées (nbPlacesPayees)
// -> chaque part ensuite partagée proportionnellement entre chevaux payables de la position concernée.
BigDecimal partParCombinaison = benefice.divide(BigDecimal.valueOf(nbPlacesPayees), SCALE, RoundingMode.HALF_UP);
// Répartition : pour chaque position (1er, 2e, 3e) on divise la part entre les chevaux classés à cette position
// puis, pour chaque cheval, on partage au prorata de ses mises sur cette position parmi les chevaux payables de la position.
// Simplification pratique: on va parcourir les positions et appliquer.
// Position 1
List<Integer> premiers = resultat.getPremiers();
repartirPlacePosition(parisPlace, premiers, partParCombinaison, res);
if (nbPlacesPayees >= 2) {
List<Integer> deuxiemes = resultat.getDeuxiemes();
repartirPlacePosition(parisPlace, deuxiemes, partParCombinaison, res);
}
if (nbPlacesPayees >= 3) {
List<Integer> troisiemes = resultat.getTroisiemes();
repartirPlacePosition(parisPlace, troisiemes, partParCombinaison, res);
}
// Note : si pour une position donnée il n'y a aucune mise, selon le règlement sa part est répartie entre les autres chevaux payables.
// Ici, si aucune mise sur une position entière, on ajoute cette part à la cagnottePlace (impl simplifiée).
}
private void repartirPlacePosition(List<Pari> parisPlace, List<Integer> chevauxPosition, BigDecimal partParCombinaison, ResultatCalcul res) {
if (chevauxPosition == null || chevauxPosition.isEmpty()) {
// pas de cheval classé à cette position -> part va en cagnotte
res.cagnottePlace = res.cagnottePlace.add(partParCombinaison);
return;
}
// total mises sur les chevaux de cette position
BigDecimal totalMises = parisPlace.stream()
.filter(p -> !p.isNonPartant() && chevauxPosition.contains(p.getNumeroCheval()))
.map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
if (totalMises.compareTo(BigDecimal.ZERO) == 0) {
// aucune mise sur cette position -> part est ajoutée à cagnotte pour redistribution (impl simplifiée)
res.cagnottePlace = res.cagnottePlace.add(partParCombinaison);
return;
}
// chaque cheval recevra une fraction proportionnelle de la partParCombinaison
for (Integer cheval : chevauxPosition) {
BigDecimal misesSurCheval = parisPlace.stream()
.filter(p -> !p.isNonPartant() && p.getNumeroCheval() == cheval)
.map(Pari::getMise).reduce(BigDecimal.ZERO, BigDecimal::add);
if (misesSurCheval.compareTo(BigDecimal.ZERO) == 0) continue;
// montant additionnel pour ce cheval = partParCombinaison * (misesSurCheval / totalMises)
BigDecimal share = partParCombinaison.multiply(misesSurCheval).divide(totalMises, SCALE, RoundingMode.HALF_UP);
// Le rapport brut pour chaque mise sur ce cheval = (share / misesSurCheval) + 1
BigDecimal ratio = share.divide(misesSurCheval, SCALE, RoundingMode.HALF_UP);
BigDecimal rapport = ratio.add(BigDecimal.ONE);
if (rapport.compareTo(RAPPORT_MIN) < 0) rapport = RAPPORT_MIN;
// appliquer paiement à tous les parieurs sur ce cheval
for (Pari p : parisPlace) {
if (p.isNonPartant()) continue;
if (p.getNumeroCheval() == cheval) {
BigDecimal gain = p.getMise().multiply(rapport).setScale(2, RoundingMode.HALF_UP);
res.gainsParParieur.put(p.getParieur(),
res.gainsParParieur.getOrDefault(p.getParieur(), BigDecimal.ZERO).add(gain));
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
package com.pmu.mali.apiplr; package com.pmumali;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

View File

@@ -0,0 +1,95 @@
package com.pmumali.ch10_multi.controller;
import com.pmumali.ch10_multi.model.*;
import com.pmumali.ch10_multi.repository.*;
import com.pmumali.ch10_multi.service.ServiceMulti;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/multi")
@RequiredArgsConstructor
public class ControleurMulti {
private final ServiceMulti serviceMulti;
private final PariMultiRepository pariRepository;
private final PaiementMultiRepository paiementRepository;
private final CagnotteMultiRepository cagnotteRepository;
@PostMapping("/pari")
public ResponseEntity<PariMulti> placerPari(@RequestBody RequetePariMulti requete) {
try {
PariMulti pari = serviceMulti.placerPari(requete);
return ResponseEntity.ok(pari);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/calculer-paiements")
public ResponseEntity<List<ReponsePaiementMulti>> calculerPaiements(@RequestBody RequeteResultatMulti requete) {
try {
List<ReponsePaiementMulti> paiements = serviceMulti.calculerPaiements(requete);
return ResponseEntity.ok(paiements);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/calcul-combinaison")
public ResponseEntity<CalculCombinaisonMulti> calculerCombinaison(
@RequestParam TypeMulti typeMulti,
@RequestParam TypeFormuleMulti typeFormule,
@RequestParam Integer nombreChevauxTotal,
@RequestParam Integer nombreChevauxBase) {
try {
CalculCombinaisonMulti resultat = serviceMulti.calculerCombinaison(
typeMulti, typeFormule, nombreChevauxTotal, nombreChevauxBase);
return ResponseEntity.ok(resultat);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/cagnotte/{cagnotteId}/utiliser")
public ResponseEntity<Void> utiliserCagnotte(
@PathVariable Long cagnotteId,
@RequestParam Long courseId) {
try {
serviceMulti.utiliserCagnotte(cagnotteId, courseId);
return ResponseEntity.ok().build();
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/cagnottes/disponibles")
public ResponseEntity<List<CagnotteMulti>> getCagnottesDisponibles() {
return ResponseEntity.ok(cagnotteRepository.findByUtilisee(false));
}
@GetMapping("/paris/course/{courseId}")
public ResponseEntity<List<PariMulti>> getParisCourse(@PathVariable Long courseId) {
return ResponseEntity.ok(pariRepository.findByCourseId(courseId));
}
@GetMapping("/paiements/pari/{pariId}")
public ResponseEntity<List<PaiementMulti>> getPaiementsPari(@PathVariable Long pariId) {
return ResponseEntity.ok(paiementRepository.findByPariId(pariId));
}
@GetMapping("/statistiques/course/{courseId}")
public ResponseEntity<Map<TypeMulti, Long>> getStatistiquesParis(@PathVariable Long courseId) {
Map<TypeMulti, Long> stats = new HashMap<>();
for (TypeMulti type : TypeMulti.values()) {
long count = pariRepository.findByCourseIdAndTypeMulti(courseId, type).size();
stats.put(type, count);
}
return ResponseEntity.ok(stats);
}
}

View File

@@ -0,0 +1,24 @@
package com.pmumali.ch10_multi.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "cagnotte_multi")
public class CagnotteMulti {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Double montant;
private LocalDateTime dateCreation;
private LocalDateTime dateUtilisation;
private Boolean utilisee;
private Long courseSourceId;
}

View File

@@ -0,0 +1,15 @@
package com.pmumali.ch10_multi.model;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class CalculCombinaisonMulti {
private TypeMulti typeMulti;
private TypeFormuleMulti typeFormule;
private Integer nombreChevauxTotal;
private Integer nombreChevauxBase;
private Integer nombreCombinaisons;
private Double valeurMise;
}

View File

@@ -0,0 +1,20 @@
package com.pmumali.ch10_multi.model;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "cheval")
public class Cheval {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private Integer numero;
private Boolean nonPartant;
}

View File

@@ -0,0 +1,29 @@
package com.pmumali.ch10_multi.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private LocalDateTime heureCourse;
private Integer nombreChevauxPartants;
@OneToMany
private List<Cheval> chevaux;
@Enumerated(EnumType.STRING)
private StatutCourse statut;
}

View File

@@ -0,0 +1,27 @@
package com.pmumali.ch10_multi.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "paiement_multi")
public class PaiementMulti {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private PariMulti pari;
private Double montant;
private LocalDateTime heurePaiement;
@Enumerated(EnumType.STRING)
private TypePaiementMulti typePaiement;
}

View File

@@ -0,0 +1,39 @@
package com.pmumali.ch10_multi.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "pari_multi")
public class PariMulti {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Course course;
@ManyToMany
private List<Cheval> chevauxSelectionnes;
private Double mise;
private LocalDateTime heurePari;
@Enumerated(EnumType.STRING)
private TypeMulti typeMulti;
@Enumerated(EnumType.STRING)
private TypeFormuleMulti typeFormule;
@ManyToOne
private Parieur parieur;
private Integer nombreChevauxBase;
}

View File

@@ -1,4 +1,4 @@
package com.pmumali.quarteplus.model; package com.pmumali.ch10_multi.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
@@ -17,4 +17,4 @@ public class Parieur {
private String nom; private String nom;
private String identification; private String identification;
private Double miseTotale; private Double miseTotale;
} }

View File

@@ -0,0 +1,13 @@
package com.pmumali.ch10_multi.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ReponsePaiementMulti {
private Long pariId;
private Double montant;
private TypePaiementMulti typePaiement;
private String message;
}

View File

@@ -0,0 +1,15 @@
package com.pmumali.ch10_multi.model;
import lombok.Data;
import java.util.List;
@Data
public class RequetePariMulti {
private Long courseId;
private List<Long> chevalIds;
private Double mise;
private TypeMulti typeMulti;
private TypeFormuleMulti typeFormule;
private Long parieurId;
private Integer nombreChevauxBase;
}

View File

@@ -0,0 +1,17 @@
package com.pmumali.ch10_multi.model;
import lombok.Data;
import java.util.List;
@Data
public class RequeteResultatMulti {
private Long courseId;
private List<Long> premiersIds;
private List<Long> secondsIds;
private List<Long> troisiemesIds;
private List<Long> quatriemesIds;
private List<Long> ordreArriveeIds;
private Double recetteNette;
private Double prelevementsLegaux;
}

View File

@@ -0,0 +1,12 @@
package com.pmumali.ch10_multi.model;
import lombok.Data;
@Data
public class ResultatCalculRapport {
private Double rapportBase;
private Double rapportMulti4;
private Double rapportMulti5;
private Double rapportMulti6;
private Double rapportMulti7;
}

View File

@@ -0,0 +1,41 @@
package com.pmumali.ch10_multi.model;
import jakarta.persistence.*;
import lombok.*;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "resultat_course")
public class ResultatCourse {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Course course;
@ManyToMany
private List<Cheval> premiers;
@ManyToMany
private List<Cheval> seconds;
@ManyToMany
private List<Cheval> troisiemes;
@ManyToMany
private List<Cheval> quatriemes;
@ManyToMany
private List<Cheval> ordreArrivee;
private Double recetteNette;
private Double montantRembourse;
private Double prelevementsLegaux;
private Double masseAPartager;
}

View File

@@ -1,5 +1,5 @@
package com.pmumali.quarteplus.model; package com.pmumali.ch10_multi.model;
public enum StatutCourse { public enum StatutCourse {
PROGRAMMEE, EN_COURS, TERMINEE, ANNULEE PROGRAMMEE, EN_COURS, TERMINEE, ANNULEE
} }

View File

@@ -1,4 +1,4 @@
package com.pmumali.quarteplus.model; package com.pmumali.ch10_multi.model;
public enum TypeDeadHeat { public enum TypeDeadHeat {
QUATRE_PREMIERS_OU_PLUS, QUATRE_PREMIERS_OU_PLUS,

View File

@@ -0,0 +1,5 @@
package com.pmumali.ch10_multi.model;
public enum TypeFormuleMulti {
UNITAIRE, CHAMP_TOTAL, CHAMP_PARTIEL
}

View File

@@ -0,0 +1,5 @@
package com.pmumali.ch10_multi.model;
public enum TypeMulti {
MULTI_4, MULTI_5, MULTI_6, MULTI_7
}

View File

@@ -0,0 +1,5 @@
package com.pmumali.ch10_multi.model;
public enum TypePaiementMulti {
MULTI_4, MULTI_5, MULTI_6, MULTI_7, REMBOURSEMENT
}

View File

@@ -0,0 +1,11 @@
package com.pmumali.ch10_multi.repository;
import com.pmumali.ch10_multi.model.CagnotteMulti;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CagnotteMultiRepository extends JpaRepository<CagnotteMulti, Long> {
List<CagnotteMulti> findByUtilisee(Boolean utilisee);
List<CagnotteMulti> findByCourseSourceId(Long courseId);
}

View File

@@ -0,0 +1,11 @@
package com.pmumali.ch10_multi.repository;
import com.pmumali.ch10_multi.model.Cheval;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ChevalRepository extends JpaRepository<Cheval, Long> {
List<Cheval> findByNonPartant(Boolean nonPartant);
List<Cheval> findByIdIn(List<Long> ids);
}

View File

@@ -0,0 +1,12 @@
package com.pmumali.ch10_multi.repository;
import com.pmumali.ch10_multi.model.Course;
import com.pmumali.ch10_multi.model.StatutCourse;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findByStatut(StatutCourse statut);
List<Course> findByNombreChevauxPartantsGreaterThanEqual(Integer nombre);
}

View File

@@ -0,0 +1,11 @@
package com.pmumali.ch10_multi.repository;
import com.pmumali.ch10_multi.model.PaiementMulti;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PaiementMultiRepository extends JpaRepository<PaiementMulti, Long> {
List<PaiementMulti> findByPariId(Long pariId);
List<PaiementMulti> findByPariCourseId(Long courseId);
}

View File

@@ -0,0 +1,14 @@
package com.pmumali.ch10_multi.repository;
import com.pmumali.ch10_multi.model.PariMulti;
import com.pmumali.ch10_multi.model.TypeFormuleMulti;
import com.pmumali.ch10_multi.model.TypeMulti;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PariMultiRepository extends JpaRepository<PariMulti, Long> {
List<PariMulti> findByCourseId(Long courseId);
List<PariMulti> findByParieurId(Long parieurId);
List<PariMulti> findByCourseIdAndTypeMulti(Long courseId, TypeMulti typeMulti);
List<PariMulti> findByCourseIdAndTypeFormule(Long courseId, TypeFormuleMulti typeFormule);
}

View File

@@ -1,6 +1,6 @@
package com.pmumali.quatro.repository; package com.pmumali.ch10_multi.repository;
import com.pmumali.quatro.model.Parieur; import com.pmumali.ch10_multi.model.Parieur;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;

View File

@@ -1,6 +1,6 @@
package com.pmumali.quarteplus.repository; package com.pmumali.ch10_multi.repository;
import com.pmumali.quarteplus.model.ResultatCourse; import com.pmumali.ch10_multi.model.ResultatCourse;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface ResultatCourseRepository extends JpaRepository<ResultatCourse, Long> { public interface ResultatCourseRepository extends JpaRepository<ResultatCourse, Long> {

View File

@@ -0,0 +1,875 @@
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 CourseRepository courseRepository;
private final ChevalRepository chevalRepository;
private final PaiementMultiRepository paiementRepository;
private final ResultatCourseRepository resultatRepository;
private final CagnotteMultiRepository cagnotteRepository;
private final ParieurRepository 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");
}
Course 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<Cheval> 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(Cheval::getNonPartant).count();
validerNonPartants(requete.getTypeMulti(), nonPartants);
// Limitation de mise selon l'article 2
Double miseEffective = Math.min(requete.getMise(), MISE_MAX);
Parieur 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) {
ResultatCourse 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, ResultatCourse resultat, Double masseAPartager) {
List<Cheval> chevauxPari = pari.getChevauxSelectionnes();
long nonPartants = chevauxPari.stream().filter(Cheval::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<Cheval> 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<Cheval> chevauxPari, ResultatCourse 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(Cheval cheval, ResultatCourse resultat) {
return estDansListe(cheval, resultat.getPremiers()) ||
estDansListe(cheval, resultat.getSeconds()) ||
estDansListe(cheval, resultat.getTroisiemes()) ||
estDansListe(cheval, resultat.getQuatriemes());
}
private boolean estDansListe(Cheval cheval, List<Cheval> 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(ResultatCourse 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(ResultatCourse 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, ResultatCourse 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, ResultatCourse resultat, Double masseAPartager) {
// Article 3a: Dead-heat de 4+ chevaux premiers
List<Cheval> 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, ResultatCourse resultat, Double masseAPartager) {
// Article 3b: Dead-heat de 3 premiers + 1+ quatrième
List<Cheval> 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, ResultatCourse resultat, Double masseAPartager) {
// Article 3c: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 2.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxPremiersUnTroisiemeUnQuatrieme(PariMulti pari, ResultatCourse resultat, Double masseAPartager) {
// Article 3d: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti gererDeadHeatTroisSeconds(PariMulti pari, ResultatCourse resultat, Double masseAPartager) {
// Article 3e: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 2.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxSecondsUnQuatrieme(PariMulti pari, ResultatCourse resultat, Double masseAPartager) {
// Article 3f: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxTroisiemes(PariMulti pari, ResultatCourse resultat, Double masseAPartager) {
// Article 3g: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti gererDeadHeatDeuxQuatriemes(PariMulti pari, ResultatCourse resultat, Double masseAPartager) {
// Article 3h: Implémentation spécifique
return calculerPaiementGeneriqueDeadHeat(pari, resultat, masseAPartager, 4.0);
}
private ReponsePaiementMulti calculerPaiementGeneriqueDeadHeat(PariMulti pari, ResultatCourse resultat,
Double masseAPartager, Double coefficient) {
// Méthode générique pour les dead-heat avec coefficient spécifique
List<Cheval> 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(ResultatCourse 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(ResultatCourse 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
ResultatCourse resultat = resultatRepository.findByCourseId(courseId);
if (resultat != null) {
resultat.setMasseAPartager(resultat.getMasseAPartager() + cagnotte.getMontant());
resultatRepository.save(resultat);
}
}
// Méthodes utilitaires
private Double calculerMasseAPartager(ResultatCourse resultat) {
// Article 5: MAP = RNET - MREMB - PRELEV
return resultat.getRecetteNette() -
resultat.getMontantRembourse() -
resultat.getPrelevementsLegaux();
}
private ResultatCourse creerResultat(RequeteResultatMulti requete) {
Course course = courseRepository.findById(requete.getCourseId())
.orElseThrow(() -> new RuntimeException("Course non trouvée"));
return ResultatCourse.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) {
ResultatCourse 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, ResultatCourse resultat) {
for (PariMulti pari : paris) {
if (estCombinaisonGagnante(pari.getChevauxSelectionnes(), resultat)) {
return true;
}
}
return false;
}
private List<ReponsePaiementMulti> appliquerReglesDegradation(ResultatCourse 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, ResultatCourse resultat,
Double masseAPartager, int niveauDegradation) {
// Implémentation des règles de dégradation selon l'article 9
List<Cheval> 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<Cheval> chevauxPari, ResultatCourse 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;
Cheval cheval1 = resultat.getOrdreArrivee().get(pos1);
Cheval cheval2 = resultat.getOrdreArrivee().get(pos2);
Cheval cheval3 = resultat.getOrdreArrivee().get(pos3);
Cheval 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) {
ResultatCourse 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());
}
}

View File

@@ -0,0 +1,67 @@
package com.pmumali.ch11_quinteplus;
import com.pmumali.ch11_quinteplus.model.Cheval;
import com.pmumali.ch11_quinteplus.model.Course;
import com.pmumali.ch11_quinteplus.repository.ChevalRepository;
import com.pmumali.ch11_quinteplus.repository.CourseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
@Component
public class InitData implements CommandLineRunner {
@Autowired
private CourseRepository repositoryCourse;
@Autowired
private ChevalRepository repositoryCheval;
@Override
public void run(String... args) throws Exception {
// Création d'une course de test
Course course = new Course();
course.setNom("Course du 15 août");
course.setDate(LocalDateTime.now().plusDays(1));
course.setLieu("Hippodrome de Bamako");
course.setEstTerminee(false);
Course courseSauvee = repositoryCourse.save(course);
// Création de chevaux de test
Cheval cheval1 = new Cheval();
cheval1.setNom("Foudre");
cheval1.setNumero(1);
cheval1.setEstNonPartant(false);
cheval1.setCourse(courseSauvee);
Cheval cheval2 = new Cheval();
cheval2.setNom("Éclair");
cheval2.setNumero(2);
cheval2.setEstNonPartant(false);
cheval2.setCourse(courseSauvee);
Cheval cheval3 = new Cheval();
cheval3.setNom("Tonnerre");
cheval3.setNumero(3);
cheval3.setEstNonPartant(false);
cheval3.setCourse(courseSauvee);
Cheval cheval4 = new Cheval();
cheval4.setNom("Vent");
cheval4.setNumero(4);
cheval4.setEstNonPartant(false);
cheval4.setCourse(courseSauvee);
Cheval cheval5 = new Cheval();
cheval5.setNom("Pluie");
cheval5.setNumero(5);
cheval5.setEstNonPartant(false);
cheval5.setCourse(courseSauvee);
repositoryCheval.saveAll(Arrays.asList(cheval1, cheval2, cheval3, cheval4, cheval5));
}
}

View File

@@ -0,0 +1,34 @@
package com.pmumali.ch11_quinteplus.controller;
import com.pmumali.ch11_quinteplus.model.Cagnotte;
import com.pmumali.ch11_quinteplus.service.ServiceCagnotte;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/cagnotte")
public class ControleurCagnotte {
@Autowired
private ServiceCagnotte serviceCagnotte;
@GetMapping("/montant")
public ResponseEntity<Double> getMontantCagnotteActive() {
return ResponseEntity.ok(serviceCagnotte.getMontantCagnotteActive());
}
@GetMapping("/historique")
public ResponseEntity<List<Cagnotte>> getHistoriqueCagnottes() {
return ResponseEntity.ok(serviceCagnotte.getHistoriqueCagnottes());
}
@PostMapping("/gerer-cas-particuliers")
public ResponseEntity<String> gererCasParticuliers() {
serviceCagnotte.gererCasParticuliersCagnotte();
return ResponseEntity.ok("Cas particuliers de cagnotte gérés avec succès");
}
}

View File

@@ -0,0 +1,55 @@
package com.pmumali.ch11_quinteplus.controller;
import com.pmumali.ch11_quinteplus.model.Course;
import com.pmumali.ch11_quinteplus.service.ServiceCourse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/courses")
public class ControleurCourse {
@Autowired
private ServiceCourse serviceCourse;
@PostMapping
public ResponseEntity<Course> creerCourse(@RequestBody Course course) {
return ResponseEntity.ok(serviceCourse.creerCourse(course));
}
@GetMapping
public ResponseEntity<List<Course>> obtenirToutesCourses() {
return ResponseEntity.ok(serviceCourse.obtenirToutesCourses());
}
@GetMapping("/{id}")
public ResponseEntity<Course> obtenirCourseParId(@PathVariable Long id) {
Course course = serviceCourse.obtenirCourseParId(id);
return course != null ? ResponseEntity.ok(course) : ResponseEntity.notFound().build();
}
@GetMapping("/terminees")
public ResponseEntity<List<Course>> obtenirCoursesTerminees() {
return ResponseEntity.ok(serviceCourse.obtenirCoursesTerminees());
}
@GetMapping("/avenir")
public ResponseEntity<List<Course>> obtenirCoursesAVenir() {
return ResponseEntity.ok(serviceCourse.obtenirCoursesAVenir());
}
@PutMapping("/{id}")
public ResponseEntity<Course> mettreAJourCourse(@PathVariable Long id, @RequestBody Course course) {
Course courseMaj = serviceCourse.mettreAJourCourse(id, course);
return courseMaj != null ? ResponseEntity.ok(courseMaj) : ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<?> supprimerCourse(@PathVariable Long id) {
boolean supprime = serviceCourse.supprimerCourse(id);
return supprime ? ResponseEntity.ok().build() : ResponseEntity.notFound().build();
}
}

View File

@@ -0,0 +1,29 @@
package com.pmumali.ch11_quinteplus.controller;
import com.pmumali.ch11_quinteplus.model.Gains;
import com.pmumali.ch11_quinteplus.model.Resultat;
import com.pmumali.ch11_quinteplus.service.ServiceGains;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/gains")
public class ControleurGains {
@Autowired
private ServiceGains serviceGains;
@PostMapping("/calculer/{courseId}")
public ResponseEntity<Gains> calculerGains(@PathVariable Long courseId, @RequestBody Resultat resultat) {
return ResponseEntity.ok(serviceGains.calculerGains(courseId, resultat));
}
@GetMapping("/course/{courseId}")
public ResponseEntity<Gains> obtenirGainsParCourse(@PathVariable Long courseId) {
Gains gains = serviceGains.obtenirGainsParCourse(courseId);
return gains != null ? ResponseEntity.ok(gains) : ResponseEntity.notFound().build();
}
}

View File

@@ -0,0 +1,51 @@
package com.pmumali.ch11_quinteplus.controller;
import com.pmumali.ch11_quinteplus.model.Pari;
import com.pmumali.ch11_quinteplus.service.ServicePari;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/paris")
public class ControleurPari {
@Autowired
private ServicePari servicePari;
@PostMapping
public ResponseEntity<Pari> placerPari(@RequestBody Pari pari) {
return ResponseEntity.ok(servicePari.placerPari(pari));
}
@GetMapping("/course/{courseId}")
public ResponseEntity<List<Pari>> obtenirParisParCourse(@PathVariable Long courseId) {
return ResponseEntity.ok(servicePari.obtenirParisParCourse(courseId));
}
@GetMapping("/parieur/{nomParieur}")
public ResponseEntity<List<Pari>> obtenirParisParParieur(@PathVariable String nomParieur) {
return ResponseEntity.ok(servicePari.obtenirParisParParieur(nomParieur));
}
@GetMapping("/{id}")
public ResponseEntity<Pari> obtenirPariParId(@PathVariable Long id) {
Pari pari = servicePari.obtenirPariParId(id);
return pari != null ? ResponseEntity.ok(pari) : ResponseEntity.notFound().build();
}
@PutMapping("/{id}")
public ResponseEntity<Pari> mettreAJourPari(@PathVariable Long id, @RequestBody Pari pari) {
Pari pariMaj = servicePari.mettreAJourPari(id, pari);
return pariMaj != null ? ResponseEntity.ok(pariMaj) : ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<?> supprimerPari(@PathVariable Long id) {
boolean supprime = servicePari.supprimerPari(id);
return supprime ? ResponseEntity.ok().build() : ResponseEntity.notFound().build();
}
}

View File

@@ -0,0 +1,36 @@
package com.pmumali.ch11_quinteplus.model;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Cagnotte {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private double montant;
private LocalDateTime dateCreation;
private LocalDateTime dateDerniereModification;
private String statut; // ACTIVE, UTILISEE, REPORTEE
@ManyToOne
@JoinColumn(name = "course_source_id")
private Course courseSource;
@ManyToOne
@JoinColumn(name = "course_destination_id")
private Course courseDestination;
private String typeCagnotte; // ORDRE_EXACT, ORDRE_INEXACT, COMBINEE
public Cagnotte(double montant, Course courseSource, String typeCagnotte) {
this.montant = montant;
this.courseSource = courseSource;
this.typeCagnotte = typeCagnotte;
this.dateCreation = LocalDateTime.now();
this.statut = "ACTIVE";
}
}

View File

@@ -0,0 +1,23 @@
package com.pmumali.ch11_quinteplus.model;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "cheval")
public class Cheval {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private int numero;
private boolean estNonPartant;
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;
}

View File

@@ -0,0 +1,32 @@
package com.pmumali.ch11_quinteplus.model;
import jakarta.persistence.*;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private LocalDateTime date;
private String lieu;
private boolean estTerminee;
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
private List<Cheval> chevaux;
@OneToOne(mappedBy = "course", cascade = CascadeType.ALL)
private Resultat resultat;
}

View File

@@ -0,0 +1,16 @@
package com.pmumali.ch11_quinteplus.model;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class CourseDto {
private Long id;
private String nom;
private LocalDateTime date;
private String lieu;
private boolean estTerminee;
private List<Long> idsChevaux;
}

View File

@@ -0,0 +1,33 @@
package com.pmumali.ch11_quinteplus.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "gains")
public class Gains {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;
private double masseOrdreExact;
private double masseOrdreInexact;
private double masseBonus4;
private double dividendeOrdreExact;
private double dividendeOrdreInexact;
private double dividendeBonus4;
private LocalDateTime dateCalcul;
private double masseCagnotte;
}

View File

@@ -0,0 +1,18 @@
package com.pmumali.ch11_quinteplus.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class GainsDto {
private Long id;
private Long courseId;
private double masseOrdreExact;
private double masseOrdreInexact;
private double masseBonus4;
private double dividendeOrdreExact;
private double dividendeOrdreInexact;
private double dividendeBonus4;
private LocalDateTime dateCalcul;
}

View File

@@ -0,0 +1,36 @@
package com.pmumali.ch11_quinteplus.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "pari")
public class Pari {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nomParieur;
private LocalDateTime datePari;
private double mise = 500.0;
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;
@ElementCollection
private List<Long> chevauxSelectionnes;
@ElementCollection
private List<Integer> ordrePredit;
private String typePari;
private boolean estPaye;
}

View File

@@ -0,0 +1,19 @@
package com.pmumali.ch11_quinteplus.model;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class PariDto {
private Long id;
private String nomParieur;
private LocalDateTime datePari;
private double mise;
private Long courseId;
private List<Long> chevauxSelectionnes;
private List<Integer> ordrePredit;
private String typePari;
private boolean estPaye;
}

View File

@@ -0,0 +1,29 @@
package com.pmumali.ch11_quinteplus.model;
import jakarta.persistence.*;
import lombok.*;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "resultat")
public class Resultat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "course_id")
private Course course;
@ElementCollection
private List<Long> idsChevauxOrdreArrivee;
private boolean aDeadHeat;
@ElementCollection
private List<Integer> positionsDeadHeat;
}

View File

@@ -0,0 +1,14 @@
package com.pmumali.ch11_quinteplus.repository;
import com.pmumali.ch11_quinteplus.model.Cagnotte;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CagnotteRepository extends JpaRepository<Cagnotte, Long> {
List<Cagnotte> findByStatut(String statut);
List<Cagnotte> findByCourseSourceId(Long courseId);
List<Cagnotte> findByCourseDestinationId(Long courseId);
List<Cagnotte> findByTypeCagnotte(String typeCagnotte);
}

View File

@@ -0,0 +1,12 @@
package com.pmumali.ch11_quinteplus.repository;
import com.pmumali.ch11_quinteplus.model.Cheval;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ChevalRepository extends JpaRepository<Cheval, Long> {
List<Cheval> findByCourseId(Long courseId);
}

View File

@@ -0,0 +1,11 @@
package com.pmumali.ch11_quinteplus.repository;
import com.pmumali.ch11_quinteplus.model.Course;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findByEstTerminee(boolean estTerminee);
}

View File

@@ -0,0 +1,10 @@
package com.pmumali.ch11_quinteplus.repository;
import com.pmumali.ch11_quinteplus.model.Gains;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GainsRepository extends JpaRepository<Gains, Long> {
Gains findByCourseId(Long courseId);
}

View File

@@ -0,0 +1,13 @@
package com.pmumali.ch11_quinteplus.repository;
import com.pmumali.ch11_quinteplus.model.Pari;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface PariRepository extends JpaRepository<Pari, Long> {
List<Pari> findByCourseId(Long courseId);
List<Pari> findByNomParieur(String nomParieur);
}

View File

@@ -0,0 +1,9 @@
package com.pmumali.ch11_quinteplus.repository;
import com.pmumali.ch11_quinteplus.model.Resultat;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ResultatRepository extends JpaRepository<Resultat,Long> {
Resultat findByCourseId(Long courseId);
}

View File

@@ -0,0 +1,251 @@
package com.pmumali.ch11_quinteplus.service;
import com.pmumali.ch11_quinteplus.model.*;
import com.pmumali.ch11_quinteplus.repository.CagnotteRepository;
import com.pmumali.ch11_quinteplus.repository.CourseRepository;
import com.pmumali.ch11_quinteplus.repository.GainsRepository;
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.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Transactional
public class ServiceCagnotte {
@Autowired
private CagnotteRepository cagnotteRepository;
@Autowired
private CourseRepository repositoryCourse;
@Autowired
private GainsRepository repositoryGains;
// Méthode principale pour gérer la cagnotte selon l'article 9
public void gererCagnotte(Gains gains, List<Pari> paris, Resultat resultat) {
Course course = gains.getCourse();
// Vérifier s'il n'y a aucun gagnant dans une catégorie
List<Pari> gagnantsOrdreExact = determinerGagnantsOrdreExact(paris, resultat);
List<Pari> gagnantsOrdreInexact = determinerGagnantsOrdreInexact(paris, resultat);
double montantCagnotte = 0.0;
boolean cagnotteOrdreExact = false;
boolean cagnotteOrdreInexact = false;
// Article 9a: Aucun gagnant dans l'ordre exact
if (gagnantsOrdreExact.isEmpty()) {
montantCagnotte += gains.getMasseOrdreExact();
gains.setMasseOrdreExact(0.0);
gains.setDividendeOrdreExact(0.0);
cagnotteOrdreExact = true;
}
// Article 9a: Aucun gagnant dans l'ordre inexact
if (gagnantsOrdreInexact.isEmpty()) {
montantCagnotte += gains.getMasseOrdreInexact();
gains.setMasseOrdreInexact(0.0);
gains.setDividendeOrdreInexact(0.0);
cagnotteOrdreInexact = true;
}
// Si une cagnotte doit être créée
if (montantCagnotte > 0) {
String typeCagnotte = determinerTypeCagnotte(cagnotteOrdreExact, cagnotteOrdreInexact);
Cagnotte cagnotte = new Cagnotte(montantCagnotte, course, typeCagnotte);
cagnotteRepository.save(cagnotte);
// Mettre à jour le gain avec le montant de la cagnotte
gains.setMasseCagnotte(montantCagnotte);
}
// Gérer le report des cagnottes existantes vers la prochaine course
reporterCagnottesVersProchaineCourse(course);
}
private String determinerTypeCagnotte(boolean ordreExact, boolean ordreInexact) {
if (ordreExact && ordreInexact) {
return "COMBINEE";
} else if (ordreExact) {
return "ORDRE_EXACT";
} else {
return "ORDRE_INEXACT";
}
}
// Méthode pour reporter les cagnottes vers la prochaine course
public void reporterCagnottesVersProchaineCourse(Course courseActuelle) {
// Trouver toutes les cagnottes actives
List<Cagnotte> cagnottesActives = cagnotteRepository.findByStatut("ACTIVE");
// Trouver la prochaine course
Course prochaineCourse = trouverProchaineCourse(courseActuelle);
if (prochaineCourse != null) {
for (Cagnotte cagnotte : cagnottesActives) {
// Article 9b: Reporter la cagnotte vers la prochaine course
cagnotte.setCourseDestination(prochaineCourse);
cagnotte.setDateDerniereModification(LocalDateTime.now());
cagnotteRepository.save(cagnotte);
}
} else {
// Article 9d: Si pas de course la semaine suivante, reporter à la prochaine disponible
for (Cagnotte cagnotte : cagnottesActives) {
cagnotte.setStatut("REPORTEE");
cagnotte.setDateDerniereModification(LocalDateTime.now());
cagnotteRepository.save(cagnotte);
}
}
}
// Méthode pour appliquer les cagnottes reportées à une course
public void appliquerCagnottesReportees(Course course) {
// Trouver toutes les cagnottes reportées pour cette course
List<Cagnotte> cagnottesReportees = cagnotteRepository.findByCourseDestinationId(course.getId());
double montantTotalCagnotte = 0.0;
for (Cagnotte cagnotte : cagnottesReportees) {
if ("REPORTEE".equals(cagnotte.getStatut())) {
montantTotalCagnotte += cagnotte.getMontant();
// Marquer la cagnotte comme utilisée
cagnotte.setStatut("UTILISEE");
cagnotte.setDateDerniereModification(LocalDateTime.now());
cagnotteRepository.save(cagnotte);
}
}
// Appliquer la cagnotte à la masse à partager de la course
if (montantTotalCagnotte > 0) {
Optional<Gains> gainsOpt = Optional.ofNullable(repositoryGains.findByCourseId(course.getId()));
if (gainsOpt.isPresent()) {
Gains gains = gainsOpt.get();
// Article 9b: Ajouter la cagnotte à la masse à partager de l'ordre exact
gains.setMasseOrdreExact(gains.getMasseOrdreExact() + montantTotalCagnotte);
gains.setMasseCagnotte(montantTotalCagnotte);
repositoryGains.save(gains);
}
}
}
// Méthode pour trouver la prochaine course après une date donnée
private Course trouverProchaineCourse(Course courseActuelle) {
List<Course> toutesCourses = repositoryCourse.findAll();
Course prochaineCourse = null;
for (Course course : toutesCourses) {
if (course.getDate().isAfter(courseActuelle.getDate()) &&
(prochaineCourse == null || course.getDate().isBefore(prochaineCourse.getDate()))) {
prochaineCourse = course;
}
}
return prochaineCourse;
}
// Méthode pour gérer les cas particuliers de cagnotte (Article 9c)
public void gererCasParticuliersCagnotte() {
// Article 9c: Si plusieurs cagnottes sont constituées dans l'intervalle,
// elles sont toutes ajoutées à la masse à partager de la prochaine course
List<Cagnotte> cagnottesActives = cagnotteRepository.findByStatut("ACTIVE");
if (cagnottesActives.size() > 1) {
// Trouver la prochaine course
Course prochaineCourse = trouverProchaineCourseParDate(LocalDateTime.now());
if (prochaineCourse != null) {
double montantTotal = 0.0;
for (Cagnotte cagnotte : cagnottesActives) {
montantTotal += cagnotte.getMontant();
cagnotte.setCourseDestination(prochaineCourse);
cagnotte.setStatut("UTILISEE");
cagnotte.setDateDerniereModification(LocalDateTime.now());
cagnotteRepository.save(cagnotte);
}
// Appliquer le montant total à la prochaine course
appliquerCagnotteACourse(prochaineCourse, montantTotal);
}
}
}
private Course trouverProchaineCourseParDate(LocalDateTime date) {
List<Course> toutesCourses = repositoryCourse.findAll();
Course prochaineCourse = null;
for (Course course : toutesCourses) {
if (course.getDate().isAfter(date) &&
(prochaineCourse == null || course.getDate().isBefore(prochaineCourse.getDate()))) {
prochaineCourse = course;
}
}
return prochaineCourse;
}
private void appliquerCagnotteACourse(Course course, double montant) {
Optional<Gains> gainsOpt = Optional.ofNullable(repositoryGains.findByCourseId(course.getId()));
if (gainsOpt.isPresent()) {
Gains gains = gainsOpt.get();
gains.setMasseOrdreExact(gains.getMasseOrdreExact() + montant);
gains.setMasseCagnotte(montant);
repositoryGains.save(gains);
} else {
// Créer un gains pour la course si il n'existe pas encore
Gains gains = new Gains();
gains.setCourse(course);
gains.setMasseOrdreExact(montant);
gains.setMasseCagnotte(montant);
gains.setDateCalcul(LocalDateTime.now());
repositoryGains.save(gains);
}
}
// Méthode pour obtenir le montant total de la cagnotte active
public double getMontantCagnotteActive() {
List<Cagnotte> cagnottesActives = cagnotteRepository.findByStatut("ACTIVE");
return cagnottesActives.stream().mapToDouble(Cagnotte::getMontant).sum();
}
// Méthode pour obtenir l'historique des cagnottes
public List<Cagnotte> getHistoriqueCagnottes() {
return cagnotteRepository.findAll();
}
// Méthodes utilitaires pour déterminer les gagnants
private List<Pari> determinerGagnantsOrdreExact(List<Pari> paris, Resultat resultat) {
return paris.stream()
.filter(pari -> estOrdreExact(pari, resultat))
.collect(Collectors.toList());
}
private List<Pari> determinerGagnantsOrdreInexact(List<Pari> paris, Resultat resultat) {
return paris.stream()
.filter(pari -> estOrdreInexact(pari, resultat))
.collect(Collectors.toList());
}
private boolean estOrdreExact(Pari pari, Resultat resultat) {
return pari.getOrdrePredit().equals(resultat.getIdsChevauxOrdreArrivee());
}
private boolean estOrdreInexact(Pari pari, Resultat resultat) {
List<Long> chevauxArrivee = resultat.getIdsChevauxOrdreArrivee();
List<Long> chevauxPari = pari.getChevauxSelectionnes();
return chevauxArrivee.containsAll(chevauxPari) &&
!pari.getOrdrePredit().equals(chevauxArrivee);
}
}

View File

@@ -0,0 +1,60 @@
package com.pmumali.ch11_quinteplus.service;
import com.pmumali.ch11_quinteplus.model.Course;
import com.pmumali.ch11_quinteplus.repository.ChevalRepository;
import com.pmumali.ch11_quinteplus.repository.CourseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ServiceCourse {
@Autowired
private CourseRepository repositoryCourse;
@Autowired
private ChevalRepository repositoryCheval;
public Course creerCourse(Course course) {
return repositoryCourse.save(course);
}
public List<Course> obtenirToutesCourses() {
return repositoryCourse.findAll();
}
public Course obtenirCourseParId(Long id) {
return repositoryCourse.findById(id).orElse(null);
}
public List<Course> obtenirCoursesTerminees() {
return repositoryCourse.findByEstTerminee(true);
}
public List<Course> obtenirCoursesAVenir() {
return repositoryCourse.findByEstTerminee(false);
}
public Course mettreAJourCourse(Long id, Course detailsCourse) {
Course course = repositoryCourse.findById(id).orElse(null);
if (course != null) {
course.setNom(detailsCourse.getNom());
course.setDate(detailsCourse.getDate());
course.setLieu(detailsCourse.getLieu());
course.setEstTerminee(detailsCourse.isEstTerminee());
return repositoryCourse.save(course);
}
return null;
}
public boolean supprimerCourse(Long id) {
if (repositoryCourse.existsById(id)) {
repositoryCourse.deleteById(id);
return true;
}
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,619 @@
package com.pmumali.ch11_quinteplus.service;
import com.pmumali.ch11_quinteplus.model.Cheval;
import com.pmumali.ch11_quinteplus.model.Pari;
import com.pmumali.ch11_quinteplus.model.Resultat;
import com.pmumali.ch11_quinteplus.repository.ChevalRepository;
import com.pmumali.ch11_quinteplus.repository.CourseRepository;
import com.pmumali.ch11_quinteplus.repository.GainsRepository;
import com.pmumali.ch11_quinteplus.repository.PariRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional
public class ServicePari {
@Autowired
private PariRepository repositoryPari;
@Autowired
private CourseRepository repositoryCourse;
@Autowired
private ChevalRepository repositoryCheval;
@Autowired
private GainsRepository repositoryGains;
private static final double MISE_DE_BASE = 500.0;
private static final double MISE_MAXIMALE = 20 * MISE_DE_BASE;
public Pari placerPari(Pari pari) {
// Vérification de la limite d'enjeu (Article 2)
if (pari.getMise() > MISE_MAXIMALE) {
pari.setMise(MISE_MAXIMALE);
// TODO: Implémenter le remboursement de la différence
}
// Vérification de la validité du pari selon le type
if (!estPariValide(pari)) {
throw new IllegalArgumentException("Pari invalide");
}
return repositoryPari.save(pari);
}
private boolean estPariValide(Pari pari) {
// Vérifier que le pari a exactement 5 chevaux sélectionnés
if (pari.getChevauxSelectionnes() == null || pari.getChevauxSelectionnes().size() != 5) {
return false;
}
// Vérifier que l'ordre prédit a exactement 5 positions
if (pari.getOrdrePredit() == null || pari.getOrdrePredit().size() != 5) {
return false;
}
// Vérifier que tous les chevaux sélectionnés existent
List<Cheval> chevaux = repositoryCheval.findAllById(pari.getChevauxSelectionnes());
if (chevaux.size() != 5) {
return false;
}
// Vérifier que la course existe
if (pari.getCourse() == null || !repositoryCourse.existsById(pari.getCourse().getId())) {
return false;
}
return true;
}
public List<Pari> obtenirParisParCourse(Long courseId) {
return repositoryPari.findByCourseId(courseId);
}
public List<Pari> obtenirParisParParieur(String nomParieur) {
return repositoryPari.findByNomParieur(nomParieur);
}
public Pari obtenirPariParId(Long id) {
return repositoryPari.findById(id).orElse(null);
}
public Pari mettreAJourPari(Long id, Pari detailsPari) {
Pari pari = repositoryPari.findById(id).orElse(null);
if (pari != null) {
pari.setNomParieur(detailsPari.getNomParieur());
pari.setMise(detailsPari.getMise());
pari.setChevauxSelectionnes(detailsPari.getChevauxSelectionnes());
pari.setOrdrePredit(detailsPari.getOrdrePredit());
pari.setTypePari(detailsPari.getTypePari());
return repositoryPari.save(pari);
}
return null;
}
public boolean supprimerPari(Long id) {
if (repositoryPari.existsById(id)) {
repositoryPari.deleteById(id);
return true;
}
return false;
}
// Méthodes pour gérer les dead-heats (Article 3)
public Map<String, Object> gererDeadHeat(Resultat resultat, List<Pari> paris) {
Map<String, Object> resultatsDeadHeat = new HashMap<>();
if (!resultat.isADeadHeat()) {
resultatsDeadHeat.put("message", "Aucun dead-heat à traiter");
return resultatsDeadHeat;
}
// Déterminer le type de dead-heat et appliquer les règles appropriées
String typeDeadHeat = determinerTypeDeadHeat(resultat);
resultatsDeadHeat.put("typeDeadHeat", typeDeadHeat);
switch (typeDeadHeat) {
case "CINQ_PREMIERS":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatCinqPremiers(resultat, paris));
break;
case "QUATRE_PREMIERS_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatQuatrePremiersUnCinquieme(resultat, paris));
break;
case "TROIS_PREMIERS_DEUX_QUATRIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatTroisPremiersDeuxQuatriemes(resultat, paris));
break;
case "TROIS_PREMIERS_UN_QUATRIEME_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatTroisPremiersUnQuatriemeUnCinquieme(resultat, paris));
break;
case "DEUX_PREMIERS_TROIS_TROISIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxPremiersTroisTroisiemes(resultat, paris));
break;
case "DEUX_PREMIERS_DEUX_TROISIEMES_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxPremiersDeuxTroisiemesUnCinquieme(resultat, paris));
break;
case "DEUX_PREMIERS_UN_TROISIEME_DEUX_QUATRIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxPremiersUnTroisiemeDeuxQuatriemes(resultat, paris));
break;
case "DEUX_PREMIERS_UN_TROISIEME_UN_QUATRIEME_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxPremiersUnTroisiemeUnQuatriemeUnCinquieme(resultat, paris));
break;
case "QUATRE_DEUXIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatQuatreDeuxiemes(resultat, paris));
break;
case "TROIS_DEUXIEMES_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatTroisDeuxiemesUnCinquieme(resultat, paris));
break;
case "DEUX_DEUXIEMES_DEUX_QUATRIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxDeuxiemesDeuxQuatriemes(resultat, paris));
break;
case "DEUX_DEUXIEMES_UN_QUATRIEME_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxDeuxiemesUnQuatriemeUnCinquieme(resultat, paris));
break;
case "TROIS_TROISIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatTroisTroisiemes(resultat, paris));
break;
case "DEUX_TROISIEMES_UN_CINQUIEME":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxTroisiemesUnCinquieme(resultat, paris));
break;
case "DEUX_QUATRIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxQuatriemes(resultat, paris));
break;
case "DEUX_CINQUIEMES":
resultatsDeadHeat.put("combinaisons", traiterDeadHeatDeuxCinquiemes(resultat, paris));
break;
default:
resultatsDeadHeat.put("erreur", "Type de dead-heat non reconnu");
}
return resultatsDeadHeat;
}
private String determinerTypeDeadHeat(Resultat resultat) {
// Implémentation de la logique pour déterminer le type de dead-heat
// Basé sur les positions avec dead-heat
List<Integer> positionsDeadHeat = resultat.getPositionsDeadHeat();
Collections.sort(positionsDeadHeat);
// Simplification: détermination basée sur les positions en dead-heat
if (positionsDeadHeat.contains(1) && positionsDeadHeat.size() >= 5) {
return "CINQ_PREMIERS";
} else if (positionsDeadHeat.contains(1) && positionsDeadHeat.contains(2)
&& positionsDeadHeat.contains(3) && positionsDeadHeat.contains(4)) {
return "QUATRE_PREMIERS_UN_CINQUIEME";
} else if (positionsDeadHeat.contains(1) && positionsDeadHeat.contains(2)
&& positionsDeadHeat.contains(3) && positionsDeadHeat.contains(4)
&& positionsDeadHeat.contains(5)) {
return "TROIS_PREMIERS_DEUX_QUATRIEMES";
}
// ... autres cas à implémenter selon l'article 3
return "AUTRE";
}
// Implémentations des différents cas de dead-heat (Article 3)
private List<Map<String, Object>> traiterDeadHeatCinqPremiers(Resultat resultat, List<Pari> paris) {
List<Map<String, Object>> combinaisonsPayables = new ArrayList<>();
// Article 3a: Dead-heat de cinq chevaux ou plus classés à la première place
// Toutes les combinaisons des chevaux classés premiers pris cinq à cinq
List<Long> chevauxPremiers = obtenirChevauxParPosition(resultat, 1);
// Générer toutes les combinaisons de 5 chevaux parmi les premiers
List<List<Long>> combinaisons = genererCombinaisons(chevauxPremiers, 5);
for (List<Long> combinaison : combinaisons) {
Map<String, Object> details = new HashMap<>();
details.put("combinaison", combinaison);
details.put("type", "QUINTE_PLUS");
details.put("permutations", 120); // 120 ordres possibles
details.put("rapportUnique", true);
combinaisonsPayables.add(details);
}
return combinaisonsPayables;
}
private List<Map<String, Object>> traiterDeadHeatQuatrePremiersUnCinquieme(Resultat resultat, List<Pari> paris) {
List<Map<String, Object>> combinaisonsPayables = new ArrayList<>();
// Article 3b: Dead-heat de quatre chevaux classés à la première place et un ou plusieurs classés cinquième
List<Long> chevauxPremiers = obtenirChevauxParPosition(resultat, 1);
List<Long> chevauxCinquiemes = obtenirChevauxParPosition(resultat, 5);
// Combinaisons des quatre premiers avec un des cinquièmes
for (Long chevalCinquieme : chevauxCinquiemes) {
List<Long> combinaison = new ArrayList<>(chevauxPremiers);
combinaison.add(chevalCinquieme);
Map<String, Object> details = new HashMap<>();
details.put("combinaison", combinaison);
details.put("type", "QUINTE_PLUS");
details.put("ordreExactPermutations", 24); // 24 permutations pour l'ordre exact
details.put("ordreInexactPermutations", 96); // 96 permutations pour l'ordre inexact
combinaisonsPayables.add(details);
}
return combinaisonsPayables;
}
private List<Map<String, Object>> traiterDeadHeatTroisPremiersDeuxQuatriemes(Resultat resultat, List<Pari> paris) {
List<Map<String, Object>> combinaisonsPayables = new ArrayList<>();
// Article 3c: Dead-heat de trois chevaux classés à la première place et deux chevaux classés à la quatrième place
List<Long> chevauxPremiers = obtenirChevauxParPosition(resultat, 1);
List<Long> chevauxQuatriemes = obtenirChevauxParPosition(resultat, 4);
// Générer toutes les combinaisons de 2 chevaux parmi les quatrièmes
List<List<Long>> combinaisonsQuatriemes = genererCombinaisons(chevauxQuatriemes, 2);
for (List<Long> combinaisonQuatriemes : combinaisonsQuatriemes) {
List<Long> combinaison = new ArrayList<>(chevauxPremiers);
combinaison.addAll(combinaisonQuatriemes);
Map<String, Object> details = new HashMap<>();
details.put("combinaison", combinaison);
details.put("type", "QUINTE_PLUS");
details.put("ordreExactPermutations", 12); // 12 permutations pour l'ordre exact
details.put("ordreInexactPermutations", 108); // 108 permutations pour l'ordre inexact
combinaisonsPayables.add(details);
}
return combinaisonsPayables;
}
// Méthodes pour les autres cas de dead-heat (à implémenter selon l'article 3)
private List<Map<String, Object>> traiterDeadHeatTroisPremiersUnQuatriemeUnCinquieme(Resultat resultat, List<Pari> paris) {
// Article 3d: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxPremiersTroisTroisiemes(Resultat resultat, List<Pari> paris) {
// Article 3e: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxPremiersDeuxTroisiemesUnCinquieme(Resultat resultat, List<Pari> paris) {
// Article 3f: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxPremiersUnTroisiemeDeuxQuatriemes(Resultat resultat, List<Pari> paris) {
// Article 3g: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxPremiersUnTroisiemeUnQuatriemeUnCinquieme(Resultat resultat, List<Pari> paris) {
// Article 3h: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatQuatreDeuxiemes(Resultat resultat, List<Pari> paris) {
// Article 3i: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatTroisDeuxiemesUnCinquieme(Resultat resultat, List<Pari> paris) {
// Article 3j: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxDeuxiemesDeuxQuatriemes(Resultat resultat, List<Pari> paris) {
// Article 3k: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxDeuxiemesUnQuatriemeUnCinquieme(Resultat resultat, List<Pari> paris) {
// Article 3l: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatTroisTroisiemes(Resultat resultat, List<Pari> paris) {
// Article 3m: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxTroisiemesUnCinquieme(Resultat resultat, List<Pari> paris) {
// Article 3n: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxQuatriemes(Resultat resultat, List<Pari> paris) {
// Article 3o: Implémentation à compléter
return new ArrayList<>();
}
private List<Map<String, Object>> traiterDeadHeatDeuxCinquiemes(Resultat resultat, List<Pari> paris) {
// Article 3p: Implémentation à compléter
return new ArrayList<>();
}
// Méthodes utilitaires pour les dead-heats
private List<Long> obtenirChevauxParPosition(Resultat resultat, int position) {
// Implémentation simplifiée: retourne les chevaux à une position donnée
// Dans une implémentation réelle, il faudrait gérer les dead-heats
List<Long> chevaux = new ArrayList<>();
if (resultat.getIdsChevauxOrdreArrivee().size() >= position) {
chevaux.add(resultat.getIdsChevauxOrdreArrivee().get(position - 1));
}
return chevaux;
}
private List<List<Long>> genererCombinaisons(List<Long> elements, int k) {
List<List<Long>> combinaisons = new ArrayList<>();
genererCombinaisonsRecursif(elements, k, 0, new ArrayList<>(), combinaisons);
return combinaisons;
}
private void genererCombinaisonsRecursif(List<Long> elements, int k, int debut,
List<Long> combinaisonCourante, List<List<Long>> combinaisons) {
if (combinaisonCourante.size() == k) {
combinaisons.add(new ArrayList<>(combinaisonCourante));
return;
}
for (int i = debut; i < elements.size(); i++) {
combinaisonCourante.add(elements.get(i));
genererCombinaisonsRecursif(elements, k, i + 1, combinaisonCourante, combinaisons);
combinaisonCourante.remove(combinaisonCourante.size() - 1);
}
}
// Méthodes pour gérer les différents types de paris (Article 7)
public Map<String, Object> calculerCoutFormule(String typeFormule, int nombreChevauxBase, int nombreChevauxSelection) {
Map<String, Object> resultat = new HashMap<>();
resultat.put("typeFormule", typeFormule);
resultat.put("nombreChevauxBase", nombreChevauxBase);
resultat.put("nombreChevauxSelection", nombreChevauxSelection);
int nombreCombinaisons = 0;
double cout = 0;
switch (typeFormule) {
case "FORMULE_SIMPLIFIEE":
nombreCombinaisons = calculerFormuleSimplifiee(nombreChevauxBase);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "FORMULE_TOUS_ORDRES":
nombreCombinaisons = calculerFormuleTousOrdres(nombreChevauxBase);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_TOTAL_4":
nombreCombinaisons = calculerChampTotal4(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_PARTIEL_4":
nombreCombinaisons = calculerChampPartiel4(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_TOTAL_3":
nombreCombinaisons = calculerChampTotal3(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_PARTIEL_3":
nombreCombinaisons = calculerChampPartiel3(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_TOTAL_2":
nombreCombinaisons = calculerChampTotal2(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_PARTIEL_2":
nombreCombinaisons = calculerChampPartiel2(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_TOTAL_1":
nombreCombinaisons = calculerChampTotal1(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
case "CHAMP_PARTIEL_1":
nombreCombinaisons = calculerChampPartiel1(nombreChevauxBase, nombreChevauxSelection);
cout = nombreCombinaisons * MISE_DE_BASE;
break;
default:
resultat.put("erreur", "Type de formule non reconnu");
return resultat;
}
resultat.put("nombreCombinaisons", nombreCombinaisons);
resultat.put("coutTotal", cout);
return resultat;
}
// Implémentation des calculs de formules selon l'article 7
private int calculerFormuleSimplifiee(int k) {
// Formule simplifiée: K x (K-1) x (K-2) x (K-3) x (K-4) / 120
if (k < 5) return 0;
return (k * (k-1) * (k-2) * (k-3) * (k-4)) / 120;
}
private int calculerFormuleTousOrdres(int k) {
// Formule tous ordres: K x (K-1) x (K-2) x (K-3) x (K-4)
if (k < 5) return 0;
return k * (k-1) * (k-2) * (k-3) * (k-4);
}
private int calculerChampTotal4(int n, int k) {
// Champ total de 4 chevaux: 120 x (N-4) paris en formule tous ordres
// ou (N-4) paris en formule simplifiée
// Ici, nous calculons la formule tous ordres
if (n < 5) return 0;
return 120 * (n - 4);
}
private int calculerChampPartiel4(int p, int k) {
// Champ partiel de 4 chevaux: 120 x P paris en formule tous ordres
// ou P paris en formule simplifiée
// Ici, nous calculons la formule tous ordres
return 120 * p;
}
private int calculerChampTotal3(int n, int k) {
// Champ total de 3 chevaux: 60 x (N-3) x (N-4) paris en formule tous ordres
if (n < 5) return 0;
return 60 * (n-3) * (n-4);
}
private int calculerChampPartiel3(int p, int k) {
// Champ partiel de 3 chevaux: 60 x P x (P-1) paris en formule tous ordres
if (p < 2) return 0;
return 60 * p * (p-1);
}
private int calculerChampTotal2(int n, int k) {
// Champ total de 2 chevaux: 20 x (N-2) x (N-3) x (N-4) paris en formule tous ordres
if (n < 5) return 0;
return 20 * (n-2) * (n-3) * (n-4);
}
private int calculerChampPartiel2(int p, int k) {
// Champ partiel de 2 chevaux: 20 x P x (P-1) x (P-2) paris en formule tous ordres
if (p < 3) return 0;
return 20 * p * (p-1) * (p-2);
}
private int calculerChampTotal1(int n, int k) {
// Champ total d'1 cheval: 5 x (N-1) x (N-2) x (N-3) x (N-4) paris en formule tous ordres
if (n < 5) return 0;
return 5 * (n-1) * (n-2) * (n-3) * (n-4);
}
private int calculerChampPartiel1(int p, int k) {
// Champ partiel d'1 cheval: 5 x P x (P-1) x (P-2) x (P-3) paris en formule tous ordres
if (p < 4) return 0;
return 5 * p * (p-1) * (p-2) * (p-3);
}
// Méthodes pour gérer les non-partants (Article 4)
public Map<String, Object> traiterNonPartants(List<Pari> paris, Resultat resultat) {
Map<String, Object> resultats = new HashMap<>();
List<Pari> parisARembourser = new ArrayList<>();
List<Pari> parisBonus4 = new ArrayList<>();
for (Pari pari : paris) {
int nombreNonPartants = compterNonPartants(pari);
if (nombreNonPartants >= 2) {
// Article 4a: Remboursement si 2 chevaux ou plus non partants
parisARembourser.add(pari);
} else if (nombreNonPartants == 1) {
// Article 4b: Bonus 4 si un seul non-partant et les 4 autres sont dans les 4 premiers
if (sontQuatrePremiers(pari, resultat)) {
parisBonus4.add(pari);
} else {
parisARembourser.add(pari);
}
}
}
resultats.put("parisARembourser", parisARembourser);
resultats.put("parisBonus4", parisBonus4);
resultats.put("montantRemboursements", calculerMontantRemboursements(parisARembourser));
return resultats;
}
private int compterNonPartants(Pari pari) {
int compte = 0;
List<Cheval> chevaux = repositoryCheval.findAllById(pari.getChevauxSelectionnes());
for (Cheval cheval : chevaux) {
if (cheval.isEstNonPartant()) {
compte++;
}
}
return compte;
}
private boolean sontQuatrePremiers(Pari pari, Resultat resultat) {
// Vérifier si au moins 4 chevaux du pari sont dans les 4 premiers
List<Long> quatrePremiers = resultat.getIdsChevauxOrdreArrivee().subList(0, Math.min(4, resultat.getIdsChevauxOrdreArrivee().size()));
List<Long> chevauxPari = pari.getChevauxSelectionnes();
long compte = chevauxPari.stream()
.filter(quatrePremiers::contains)
.count();
return compte >= 4;
}
private double calculerMontantRemboursements(List<Pari> paris) {
return paris.stream()
.mapToDouble(Pari::getMise)
.sum();
}
// Méthode pour déterminer les gagnants d'une course
public Map<String, Object> determinerGagnants(Long courseId, Resultat resultat) {
Map<String, Object> resultats = new HashMap<>();
List<Pari> tousParis = repositoryPari.findByCourseId(courseId);
List<Pari> gagnantsOrdreExact = new ArrayList<>();
List<Pari> gagnantsOrdreInexact = new ArrayList<>();
List<Pari> gagnantsBonus4 = new ArrayList<>();
for (Pari pari : tousParis) {
if (estGagnantOrdreExact(pari, resultat)) {
gagnantsOrdreExact.add(pari);
} else if (estGagnantOrdreInexact(pari, resultat)) {
gagnantsOrdreInexact.add(pari);
} else if (estGagnantBonus4(pari, resultat)) {
gagnantsBonus4.add(pari);
}
}
resultats.put("gagnantsOrdreExact", gagnantsOrdreExact);
resultats.put("gagnantsOrdreInexact", gagnantsOrdreInexact);
resultats.put("gagnantsBonus4", gagnantsBonus4);
return resultats;
}
private boolean estGagnantOrdreExact(Pari pari, Resultat resultat) {
// Vérifier si l'ordre prédit correspond exactement à l'ordre d'arrivée
return pari.getOrdrePredit().equals(resultat.getIdsChevauxOrdreArrivee());
}
private boolean estGagnantOrdreInexact(Pari pari, Resultat resultat) {
// Vérifier si les 5 chevaux sont dans les 5 premiers mais pas dans le bon ordre
List<Long> cinqPremiers = resultat.getIdsChevauxOrdreArrivee().subList(0, Math.min(5, resultat.getIdsChevauxOrdreArrivee().size()));
List<Long> chevauxPari = pari.getChevauxSelectionnes();
return cinqPremiers.containsAll(chevauxPari) &&
!pari.getOrdrePredit().equals(resultat.getIdsChevauxOrdreArrivee());
}
private boolean estGagnantBonus4(Pari pari, Resultat resultat) {
// Vérifier si au moins 4 chevaux du pari sont dans les 4 premiers
List<Long> quatrePremiers = resultat.getIdsChevauxOrdreArrivee().subList(0, Math.min(4, resultat.getIdsChevauxOrdreArrivee().size()));
List<Long> chevauxPari = pari.getChevauxSelectionnes();
long compte = chevauxPari.stream()
.filter(quatrePremiers::contains)
.count();
return compte >= 4;
}
}

View File

@@ -1,11 +1,11 @@
// CourseController.java // CourseController.java
package com.pmumali.jumeleordre.controller; package com.pmumali.ch4_jumeleordre.controller;
import com.pmumali.jumeleordre.dto.ResultatCourseDto; import com.pmumali.ch4_jumeleordre.dto.ResultatCourseDto;
import com.pmumali.jumeleordre.exception.ResultatCourseInvalideException; import com.pmumali.ch4_jumeleordre.exception.ResultatCourseInvalideException;
import com.pmumali.jumeleordre.model.Course; import com.pmumali.ch4_jumeleordre.model.Course;
import com.pmumali.jumeleordre.service.ResultatCourseService; import com.pmumali.ch4_jumeleordre.service.ResultatCourseService;
import com.pmumali.jumeleordre.service.CourseService; import com.pmumali.ch4_jumeleordre.service.CourseService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -1,11 +1,11 @@
// ParisController.java // ParisController.java
package com.pmumali.jumeleordre.controller; package com.pmumali.ch4_jumeleordre.controller;
import com.pmumali.jumeleordre.dto.ParisDto; import com.pmumali.ch4_jumeleordre.dto.ParisDto;
import com.pmumali.jumeleordre.dto.GainsDto; import com.pmumali.ch4_jumeleordre.dto.GainsDto;
import com.pmumali.jumeleordre.exception.ParisInvalideException; import com.pmumali.ch4_jumeleordre.exception.ParisInvalideException;
import com.pmumali.jumeleordre.model.Paris; import com.pmumali.ch4_jumeleordre.model.Paris;
import com.pmumali.jumeleordre.service.ParisService; import com.pmumali.ch4_jumeleordre.service.ParisService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -1,5 +1,5 @@
// GainsDto.java // GainsDto.java
package com.pmumali.jumeleordre.dto; package com.pmumali.ch4_jumeleordre.dto;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,5 @@
// ParisDto.java // ParisDto.java
package com.pmumali.jumeleordre.dto; package com.pmumali.ch4_jumeleordre.dto;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,5 @@
// ResultatCourseDto.java // ResultatCourseDto.java
package com.pmumali.jumeleordre.dto; package com.pmumali.ch4_jumeleordre.dto;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,5 @@
// ChevalInvalideException.java // ChevalInvalideException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class ChevalInvalideException extends JumeleOrdreException { public class ChevalInvalideException extends JumeleOrdreException {
public ChevalInvalideException(String message) { public ChevalInvalideException(String message) {

View File

@@ -1,5 +1,5 @@
// CourseDejaTermineeException.java // CourseDejaTermineeException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class CourseDejaTermineeException extends CourseInvalideException { public class CourseDejaTermineeException extends CourseInvalideException {
public CourseDejaTermineeException(Long idCourse) { public CourseDejaTermineeException(Long idCourse) {

View File

@@ -1,5 +1,5 @@
// CourseInvalideException.java // CourseInvalideException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class CourseInvalideException extends JumeleOrdreException { public class CourseInvalideException extends JumeleOrdreException {
public CourseInvalideException(String message) { public CourseInvalideException(String message) {

View File

@@ -1,4 +1,4 @@
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;

View File

@@ -1,4 +1,4 @@
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class JumeleOrdreException extends RuntimeException { public class JumeleOrdreException extends RuntimeException {
public JumeleOrdreException(String message) { public JumeleOrdreException(String message) {

View File

@@ -1,5 +1,5 @@
// LimiteMiseDepasseeException.java // LimiteMiseDepasseeException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class LimiteMiseDepasseeException extends ParisInvalideException { public class LimiteMiseDepasseeException extends ParisInvalideException {
private final double miseActuelle; private final double miseActuelle;

View File

@@ -1,5 +1,5 @@
// NombreChevauxInvalideException.java // NombreChevauxInvalideException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class NombreChevauxInvalideException extends CourseInvalideException { public class NombreChevauxInvalideException extends CourseInvalideException {
private final int nombreChevaux; private final int nombreChevaux;

View File

@@ -1,5 +1,5 @@
// PaiementInvalideException.java // PaiementInvalideException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class PaiementInvalideException extends JumeleOrdreException { public class PaiementInvalideException extends JumeleOrdreException {
public PaiementInvalideException(String message) { public PaiementInvalideException(String message) {

View File

@@ -1,5 +1,5 @@
// ParisInvalideException.java // ParisInvalideException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class ParisInvalideException extends JumeleOrdreException { public class ParisInvalideException extends JumeleOrdreException {
public ParisInvalideException(String message) { public ParisInvalideException(String message) {

View File

@@ -1,5 +1,5 @@
// ResultatCourseInvalideException.java // ResultatCourseInvalideException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
public class ResultatCourseInvalideException extends JumeleOrdreException { public class ResultatCourseInvalideException extends JumeleOrdreException {
public ResultatCourseInvalideException(String message) { public ResultatCourseInvalideException(String message) {

View File

@@ -1,5 +1,5 @@
// ValidationException.java // ValidationException.java
package com.pmumali.jumeleordre.exception; package com.pmumali.ch4_jumeleordre.exception;
import java.util.List; import java.util.List;

View File

@@ -1,4 +1,4 @@
package com.pmumali.jumeleordre.model; package com.pmumali.ch4_jumeleordre.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,5 @@
// Course.java // Course.java
package com.pmumali.jumeleordre.model; package com.pmumali.ch4_jumeleordre.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,5 @@
// Paris.java // Paris.java
package com.pmumali.jumeleordre.model; package com.pmumali.ch4_jumeleordre.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,5 @@
// ResultatCourse.java // ResultatCourse.java
package com.pmumali.jumeleordre.model; package com.pmumali.ch4_jumeleordre.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import lombok.Data;

View File

@@ -1,7 +1,7 @@
// ChevalRepository.java // ChevalRepository.java
package com.pmumali.jumeleordre.repository; package com.pmumali.ch4_jumeleordre.repository;
import com.pmumali.jumeleordre.model.Cheval; import com.pmumali.ch4_jumeleordre.model.Cheval;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface ChevalRepository extends JpaRepository<Cheval, Long> { public interface ChevalRepository extends JpaRepository<Cheval, Long> {

View File

@@ -1,7 +1,7 @@
// CourseRepository.java // CourseRepository.java
package com.pmumali.jumeleordre.repository; package com.pmumali.ch4_jumeleordre.repository;
import com.pmumali.jumeleordre.model.Course; import com.pmumali.ch4_jumeleordre.model.Course;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;

View File

@@ -1,7 +1,7 @@
// ParisRepository.java // ParisRepository.java
package com.pmumali.jumeleordre.repository; package com.pmumali.ch4_jumeleordre.repository;
import com.pmumali.jumeleordre.model.Paris; import com.pmumali.ch4_jumeleordre.model.Paris;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;

View File

@@ -1,7 +1,7 @@
// ResultatCourseRepository.java // ResultatCourseRepository.java
package com.pmumali.jumeleordre.repository; package com.pmumali.ch4_jumeleordre.repository;
import com.pmumali.jumeleordre.model.ResultatCourse; import com.pmumali.ch4_jumeleordre.model.ResultatCourse;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface ResultatCourseRepository extends JpaRepository<ResultatCourse, Long> { public interface ResultatCourseRepository extends JpaRepository<ResultatCourse, Long> {

View File

@@ -1,16 +1,15 @@
package com.pmumali.jumeleordre.service; package com.pmumali.ch4_jumeleordre.service;
import com.pmumali.jumeleordre.model.Course; import com.pmumali.ch4_jumeleordre.model.Course;
import com.pmumali.jumeleordre.model.Cheval; import com.pmumali.ch4_jumeleordre.model.Cheval;
import com.pmumali.jumeleordre.repository.CourseRepository; import com.pmumali.ch4_jumeleordre.repository.CourseRepository;
import com.pmumali.jumeleordre.repository.ChevalRepository; import com.pmumali.ch4_jumeleordre.repository.ChevalRepository;
import com.pmumali.jumeleordre.exception.CourseInvalideException; import com.pmumali.ch4_jumeleordre.exception.CourseInvalideException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional;
@Service @Service
public class CourseService { public class CourseService {

View File

@@ -1,14 +1,14 @@
// ParisService.java // ParisService.java
package com.pmumali.jumeleordre.service; package com.pmumali.ch4_jumeleordre.service;
import com.pmumali.jumeleordre.dto.ParisDto; import com.pmumali.ch4_jumeleordre.dto.ParisDto;
import com.pmumali.jumeleordre.exception.ParisInvalideException; import com.pmumali.ch4_jumeleordre.exception.ParisInvalideException;
import com.pmumali.jumeleordre.model.Cheval; import com.pmumali.ch4_jumeleordre.model.Cheval;
import com.pmumali.jumeleordre.model.Course; import com.pmumali.ch4_jumeleordre.model.Course;
import com.pmumali.jumeleordre.model.Paris; import com.pmumali.ch4_jumeleordre.model.Paris;
import com.pmumali.jumeleordre.repository.ChevalRepository; import com.pmumali.ch4_jumeleordre.repository.ChevalRepository;
import com.pmumali.jumeleordre.repository.CourseRepository; import com.pmumali.ch4_jumeleordre.repository.CourseRepository;
import com.pmumali.jumeleordre.repository.ParisRepository; import com.pmumali.ch4_jumeleordre.repository.ParisRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

View File

@@ -1,16 +1,15 @@
// ResultatCourseService.java // ResultatCourseService.java
package com.pmumali.jumeleordre.service; package com.pmumali.ch4_jumeleordre.service;
import com.pmumali.jumeleordre.dto.ResultatCourseDto; import com.pmumali.ch4_jumeleordre.dto.ResultatCourseDto;
import com.pmumali.jumeleordre.exception.ResultatCourseInvalideException; import com.pmumali.ch4_jumeleordre.exception.ResultatCourseInvalideException;
import com.pmumali.jumeleordre.model.*; import com.pmumali.ch4_jumeleordre.model.*;
import com.pmumali.jumeleordre.repository.*; import com.pmumali.ch4_jumeleordre.repository.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Service @Service
public class ResultatCourseService { public class ResultatCourseService {

View File

@@ -1,8 +1,8 @@
package com.pmumali.trio.controller; package com.pmumali.ch5_trio.controller;
import com.pmumali.trio.dto.ParisTrioDto; import com.pmumali.ch5_trio.dto.ParisTrioDto;
import com.pmumali.trio.model.ParisTrio; import com.pmumali.ch5_trio.model.ParisTrio;
import com.pmumali.trio.service.ParisTrioService; import com.pmumali.ch5_trio.service.ParisTrioService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -1,7 +1,7 @@
package com.pmumali.trio.controller; package com.pmumali.ch5_trio.controller;
import com.pmumali.trio.dto.ResultatCourseDto; import com.pmumali.ch5_trio.dto.ResultatCourseDto;
import com.pmumali.trio.service.ResultatCourseService; import com.pmumali.ch5_trio.service.ResultatCourseService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;

View File

@@ -1,6 +1,6 @@
package com.pmumali.trio.dto; package com.pmumali.ch5_trio.dto;
import com.pmumali.trio.model.ParisTrio.TypeFormule; import com.pmumali.ch5_trio.model.ParisTrio.TypeFormule;
import lombok.Data; import lombok.Data;
@Data @Data

View File

@@ -1,4 +1,4 @@
package com.pmumali.trio.dto; package com.pmumali.ch5_trio.dto;
import lombok.Data; import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.pmumali.trio.exception; package com.pmumali.ch5_trio.exception;
public class ChevalNonPartantException extends ParisTrioInvalideException { public class ChevalNonPartantException extends ParisTrioInvalideException {
public ChevalNonPartantException(String nomCheval) { public ChevalNonPartantException(String nomCheval) {

View File

@@ -1,4 +1,4 @@
package com.pmumali.trio.exception; package com.pmumali.ch5_trio.exception;
public class CourseDejaTermineeException extends ParisTrioInvalideException { public class CourseDejaTermineeException extends ParisTrioInvalideException {
public CourseDejaTermineeException(Long idCourse) { public CourseDejaTermineeException(Long idCourse) {

View File

@@ -1,7 +1,7 @@
// CourseInvalideException.java // CourseInvalideException.java
package com.pmumali.trio.exception; package com.pmumali.ch5_trio.exception;
import com.pmumali.jumeleordre.exception.JumeleOrdreException; import com.pmumali.ch4_jumeleordre.exception.JumeleOrdreException;
public class CourseInvalideException extends JumeleOrdreException { public class CourseInvalideException extends JumeleOrdreException {
public CourseInvalideException(String message) { public CourseInvalideException(String message) {

View File

@@ -1,4 +1,4 @@
package com.pmumali.trio.exception; package com.pmumali.ch5_trio.exception;
public class LimiteMiseDepasseeException extends ParisTrioInvalideException { public class LimiteMiseDepasseeException extends ParisTrioInvalideException {
private final double miseActuelle; private final double miseActuelle;

Some files were not shown because too many files have changed in this diff Show More