Fin intégration jumele gagnant
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package com.pmumali.ch1_simple.controller;
|
||||
|
||||
import com.pmumali.ch1_simple.model.Cheval;
|
||||
import com.pmumali.ch1_simple.service.ChevalService;
|
||||
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/chevaux")
|
||||
public class ChevalController {
|
||||
|
||||
@Autowired
|
||||
private ChevalService chevalService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Cheval> ajouterCheval(@RequestBody Cheval cheval) {
|
||||
return ResponseEntity.ok(chevalService.ajouterCheval(cheval));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}")
|
||||
public ResponseEntity<List<Cheval>> obtenirChevauxParCourse(@PathVariable Long courseId) {
|
||||
return ResponseEntity.ok(chevalService.obtenirChevauxParCourse(courseId));
|
||||
}
|
||||
|
||||
@GetMapping("/ecurie/{nomEcurie}")
|
||||
public ResponseEntity<List<Cheval>> obtenirChevauxParEcurie(@PathVariable String nomEcurie) {
|
||||
return ResponseEntity.ok(chevalService.obtenirChevauxParEcurie(nomEcurie));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.pmumali.ch1_simple.controller;
|
||||
|
||||
import com.pmumali.ch1_simple.model.Course;
|
||||
import com.pmumali.ch1_simple.service.CourseService;
|
||||
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 CourseController {
|
||||
|
||||
@Autowired
|
||||
private CourseService courseService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Course> creerCourse(@RequestBody Course course) {
|
||||
return ResponseEntity.ok(courseService.creerCourse(course));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Course>> obtenirToutesCourses() {
|
||||
return ResponseEntity.ok(courseService.obtenirToutesCourses());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Course> obtenirCourseParId(@PathVariable Long id) {
|
||||
Course course = courseService.obtenirCourseParId(id);
|
||||
return course != null ? ResponseEntity.ok(course) : ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@GetMapping("/terminees")
|
||||
public ResponseEntity<List<Course>> obtenirCoursesTerminees() {
|
||||
return ResponseEntity.ok(courseService.obtenirCoursesTerminees());
|
||||
}
|
||||
|
||||
@GetMapping("/avenir")
|
||||
public ResponseEntity<List<Course>> obtenirCoursesAVenir() {
|
||||
return ResponseEntity.ok(courseService.obtenirCoursesAVenir());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.pmumali.ch1_simple.controller;
|
||||
|
||||
import com.pmumali.ch1_simple.model.Gains;
|
||||
import com.pmumali.ch1_simple.model.ResultatCourse;
|
||||
import com.pmumali.ch1_simple.service.GainsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/gains")
|
||||
public class GainsController {
|
||||
|
||||
@Autowired
|
||||
private GainsService gainsService;
|
||||
|
||||
@PostMapping("/calculer/{courseId}")
|
||||
public ResponseEntity<Gains> calculerGains(@PathVariable Long courseId, @RequestBody ResultatCourse resultat) {
|
||||
return ResponseEntity.ok(gainsService.calculerGains(courseId, resultat));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}")
|
||||
public ResponseEntity<Gains> obtenirGainsParCourse(@PathVariable Long courseId) {
|
||||
Gains gains = gainsService.obtenirGainsParCourse(courseId);
|
||||
return gains != null ? ResponseEntity.ok(gains) : ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.pmumali.ch1_simple.controller;
|
||||
|
||||
import com.pmumali.ch1_simple.model.PariSimple;
|
||||
import com.pmumali.ch1_simple.service.PariSimpleService;
|
||||
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 PariController {
|
||||
|
||||
@Autowired
|
||||
private PariSimpleService pariService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<PariSimple> placerPari(@RequestBody PariSimple pari) {
|
||||
return ResponseEntity.ok(pariService.placerPari(pari));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}")
|
||||
public ResponseEntity<List<PariSimple>> obtenirParisParCourse(@PathVariable Long courseId) {
|
||||
return ResponseEntity.ok(pariService.obtenirParisParCourse(courseId));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}/type/{typePari}")
|
||||
public ResponseEntity<List<PariSimple>> obtenirParisParCourseEtType(
|
||||
@PathVariable Long courseId, @PathVariable String typePari) {
|
||||
return ResponseEntity.ok(pariService.obtenirParisParCourseEtType(courseId, typePari));
|
||||
}
|
||||
|
||||
@GetMapping("/cheval/{chevalId}")
|
||||
public ResponseEntity<List<PariSimple>> obtenirParisParCheval(@PathVariable Long chevalId) {
|
||||
return ResponseEntity.ok(pariService.obtenirParisParCheval(chevalId));
|
||||
}
|
||||
}
|
||||
|
||||
13
src/main/java/com/pmumali/ch1_simple/dto/ChevalDto.java
Normal file
13
src/main/java/com/pmumali/ch1_simple/dto/ChevalDto.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.pmumali.ch1_simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChevalDto {
|
||||
private Long id;
|
||||
private String nom;
|
||||
private int numero;
|
||||
private boolean estNonPartant;
|
||||
private String nomEcurie;
|
||||
private Long courseId;
|
||||
}
|
||||
16
src/main/java/com/pmumali/ch1_simple/dto/CourseDto.java
Normal file
16
src/main/java/com/pmumali/ch1_simple/dto/CourseDto.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.pmumali.ch1_simple.dto;
|
||||
|
||||
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 int nombreChevauxInscrits;
|
||||
private boolean estTerminee;
|
||||
private List<Long> chevauxIds;
|
||||
}
|
||||
17
src/main/java/com/pmumali/ch1_simple/dto/GainsDto.java
Normal file
17
src/main/java/com/pmumali/ch1_simple/dto/GainsDto.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.pmumali.ch1_simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class GainsDto {
|
||||
private Long id;
|
||||
private Long courseId;
|
||||
private double masseGagnant;
|
||||
private double massePlace;
|
||||
private double rapportGagnant;
|
||||
private double rapportPlace;
|
||||
private double montantCagnotte;
|
||||
private LocalDateTime dateCalcul;
|
||||
}
|
||||
16
src/main/java/com/pmumali/ch1_simple/dto/PariSimpleDto.java
Normal file
16
src/main/java/com/pmumali/ch1_simple/dto/PariSimpleDto.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.pmumali.ch1_simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class PariSimpleDto {
|
||||
private Long id;
|
||||
private String typePari;
|
||||
private double mise;
|
||||
private LocalDateTime datePari;
|
||||
private Long chevalId;
|
||||
private Long courseId;
|
||||
private boolean estPaye;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.pmumali.ch1_simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ResultatCourseDto {
|
||||
private Long id;
|
||||
private Long courseId;
|
||||
private List<Long> chevauxPremiers;
|
||||
private List<Long> chevauxDeuxiemes;
|
||||
private List<Long> chevauxTroisiemes;
|
||||
private boolean aDeadHeat;
|
||||
}
|
||||
25
src/main/java/com/pmumali/ch1_simple/model/Cheval.java
Normal file
25
src/main/java/com/pmumali/ch1_simple/model/Cheval.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.pmumali.ch1_simple.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;
|
||||
private String nomEcurie;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "course_id")
|
||||
private Course course;
|
||||
}
|
||||
|
||||
32
src/main/java/com/pmumali/ch1_simple/model/Course.java
Normal file
32
src/main/java/com/pmumali/ch1_simple/model/Course.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.pmumali.ch1_simple.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
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 int nombreChevauxInscrits;
|
||||
private boolean estTerminee;
|
||||
|
||||
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
|
||||
private List<Cheval> chevaux;
|
||||
|
||||
@OneToOne(mappedBy = "course", cascade = CascadeType.ALL)
|
||||
private ResultatCourse resultat;
|
||||
}
|
||||
31
src/main/java/com/pmumali/ch1_simple/model/Gains.java
Normal file
31
src/main/java/com/pmumali/ch1_simple/model/Gains.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.pmumali.ch1_simple.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 masseGagnant;
|
||||
private double massePlace;
|
||||
|
||||
private double rapportGagnant;
|
||||
private double rapportPlace;
|
||||
|
||||
private double montantCagnotte;
|
||||
|
||||
private LocalDateTime dateCalcul;
|
||||
}
|
||||
31
src/main/java/com/pmumali/ch1_simple/model/PariSimple.java
Normal file
31
src/main/java/com/pmumali/ch1_simple/model/PariSimple.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.pmumali.ch1_simple.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "pari_simple")
|
||||
public class PariSimple {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String typePari; // GAGNANT ou PLACE
|
||||
private double mise = 500.0;
|
||||
private LocalDateTime datePari;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "cheval_id")
|
||||
private Cheval cheval;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "course_id")
|
||||
private Course course;
|
||||
|
||||
private boolean estPaye;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.pmumali.ch1_simple.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "resultat_course")
|
||||
public class ResultatCourse {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "course_id")
|
||||
private Course course;
|
||||
|
||||
@ElementCollection
|
||||
private List<Long> chevauxPremiers; // Pour gérer les dead-heats
|
||||
|
||||
@ElementCollection
|
||||
private List<Long> chevauxDeuxiemes; // Pour gérer les dead-heats
|
||||
|
||||
@ElementCollection
|
||||
private List<Long> chevauxTroisiemes; // Pour gérer les dead-heats
|
||||
|
||||
private boolean aDeadHeat;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.pmumali.ch1_simple.repository;
|
||||
|
||||
import com.pmumali.ch1_simple.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);
|
||||
List<Cheval> findByNomEcurie(String nomEcurie);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.pmumali.ch1_simple.repository;
|
||||
|
||||
import com.pmumali.ch1_simple.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);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.pmumali.ch1_simple.repository;
|
||||
|
||||
import com.pmumali.ch1_simple.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);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.pmumali.ch1_simple.repository;
|
||||
|
||||
import com.pmumali.ch1_simple.model.PariSimple;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface PariSimpleRepository extends JpaRepository<PariSimple, Long> {
|
||||
List<PariSimple> findByCourseId(Long courseId);
|
||||
List<PariSimple> findByCourseIdAndTypePari(Long courseId, String typePari);
|
||||
List<PariSimple> findByChevalId(Long chevalId);
|
||||
List<PariSimple> findByChevalIdAndTypePari(Long chevalId, String typePari);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.pmumali.ch1_simple.repository;
|
||||
|
||||
import com.pmumali.ch1_simple.model.ResultatCourse;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ResultatCourseRepository extends JpaRepository<ResultatCourse, Long> {
|
||||
ResultatCourse findByCourseId(Long courseId);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.pmumali.ch1_simple.service;
|
||||
|
||||
import com.pmumali.ch1_simple.model.Cheval;
|
||||
import com.pmumali.ch1_simple.repository.ChevalRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ChevalService {
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
public Cheval ajouterCheval(Cheval cheval) {
|
||||
return chevalRepository.save(cheval);
|
||||
}
|
||||
|
||||
public List<Cheval> obtenirChevauxParCourse(Long courseId) {
|
||||
return chevalRepository.findByCourseId(courseId);
|
||||
}
|
||||
|
||||
public List<Cheval> obtenirChevauxParEcurie(String nomEcurie) {
|
||||
return chevalRepository.findByNomEcurie(nomEcurie);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.pmumali.ch1_simple.service;
|
||||
|
||||
|
||||
import com.pmumali.ch1_simple.model.Course;
|
||||
import com.pmumali.ch1_simple.repository.CourseRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
||||
@Service
|
||||
public class CourseService {
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
public Course creerCourse(Course course) {
|
||||
return courseRepository.save(course);
|
||||
}
|
||||
|
||||
public List<Course> obtenirToutesCourses() {
|
||||
return courseRepository.findAll();
|
||||
}
|
||||
|
||||
public Course obtenirCourseParId(Long id) {
|
||||
return courseRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public List<Course> obtenirCoursesTerminees() {
|
||||
return courseRepository.findByEstTerminee(true);
|
||||
}
|
||||
|
||||
public List<Course> obtenirCoursesAVenir() {
|
||||
return courseRepository.findByEstTerminee(false);
|
||||
}
|
||||
}
|
||||
479
src/main/java/com/pmumali/ch1_simple/service/GainsService.java
Normal file
479
src/main/java/com/pmumali/ch1_simple/service/GainsService.java
Normal file
@@ -0,0 +1,479 @@
|
||||
package com.pmumali.ch1_simple.service;
|
||||
|
||||
import com.pmumali.ch1_simple.model.*;
|
||||
import com.pmumali.ch1_simple.repository.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class GainsService {
|
||||
|
||||
@Autowired
|
||||
private PariSimpleRepository pariRepository;
|
||||
|
||||
@Autowired
|
||||
private GainsRepository gainsRepository;
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
@Autowired
|
||||
private ResultatCourseRepository resultatRepository;
|
||||
|
||||
private static final double RAPPORT_MINIMUM = 1.1;
|
||||
private static final double PRELEVEMENTS = 0.15; // 15% de prélèvements
|
||||
|
||||
public Gains calculerGains(Long courseId, ResultatCourse resultat) {
|
||||
Course course = courseRepository.findById(courseId)
|
||||
.orElseThrow(() -> new RuntimeException("Course non trouvée"));
|
||||
|
||||
List<PariSimple> tousParis = pariRepository.findByCourseId(courseId);
|
||||
|
||||
// Calcul de la recette nette (Article 5)
|
||||
double totalMises = tousParis.stream().mapToDouble(PariSimple::getMise).sum();
|
||||
double recetteNette = totalMises;
|
||||
|
||||
// Calcul des remboursements (Article 4)
|
||||
double montantRemboursements = calculerRemboursements(tousParis);
|
||||
|
||||
// Masse à partager (Article 5)
|
||||
double masseAPartager = recetteNette - montantRemboursements - (recetteNette * PRELEVEMENTS);
|
||||
|
||||
// Séparation des paris GAGNANT et PLACE
|
||||
List<PariSimple> parisGagnant = tousParis.stream()
|
||||
.filter(p -> "GAGNANT".equals(p.getTypePari()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<PariSimple> parisPlace = tousParis.stream()
|
||||
.filter(p -> "PLACE".equals(p.getTypePari()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Calcul des masses à partager pour chaque type de pari
|
||||
double masseGagnant = masseAPartager * (parisGagnant.stream().mapToDouble(PariSimple::getMise).sum() / totalMises);
|
||||
double massePlace = masseAPartager * (parisPlace.stream().mapToDouble(PariSimple::getMise).sum() / totalMises);
|
||||
|
||||
// Calcul des rapports
|
||||
Gains gains = new Gains();
|
||||
gains.setCourse(course);
|
||||
gains.setMasseGagnant(masseGagnant);
|
||||
gains.setMassePlace(massePlace);
|
||||
gains.setDateCalcul(LocalDateTime.now());
|
||||
|
||||
// Calcul détaillé des gains selon les règles
|
||||
calculerDetailsGains(gains, resultat, parisGagnant, parisPlace);
|
||||
|
||||
// Gérer la cagnotte si nécessaire (Article 6 et 7)
|
||||
gererCagnotte(gains, resultat, parisGagnant, parisPlace);
|
||||
|
||||
return gainsRepository.save(gains);
|
||||
}
|
||||
|
||||
private double calculerRemboursements(List<PariSimple> paris) {
|
||||
double remboursements = 0.0;
|
||||
|
||||
for (PariSimple pari : paris) {
|
||||
if (pari.getCheval().isEstNonPartant()) {
|
||||
remboursements += pari.getMise();
|
||||
pari.setEstPaye(true); // Marquer comme remboursé
|
||||
}
|
||||
}
|
||||
|
||||
return remboursements;
|
||||
}
|
||||
|
||||
private void calculerDetailsGains(Gains gains, ResultatCourse resultat,
|
||||
List<PariSimple> parisGagnant, List<PariSimple> parisPlace) {
|
||||
// Calcul des rapports GAGNANT
|
||||
if (resultat.isADeadHeat()) {
|
||||
calculerRapportsGagnantDeadHeat(gains, resultat, parisGagnant);
|
||||
} else {
|
||||
calculerRapportsGagnantNormal(gains, resultat, parisGagnant);
|
||||
}
|
||||
|
||||
// Calcul des rapports PLACE
|
||||
if (resultat.isADeadHeat()) {
|
||||
calculerRapportsPlaceDeadHeat(gains, resultat, parisPlace);
|
||||
} else {
|
||||
calculerRapportsPlaceNormal(gains, resultat, parisPlace);
|
||||
}
|
||||
|
||||
// Garantir un rapport minimum de 1.1 par unité de mise (Article 5d)
|
||||
gains.setRapportGagnant(Math.max(gains.getRapportGagnant(), RAPPORT_MINIMUM));
|
||||
gains.setRapportPlace(Math.max(gains.getRapportPlace(), RAPPORT_MINIMUM));
|
||||
}
|
||||
|
||||
private void calculerRapportsGagnantNormal(Gains gains, ResultatCourse resultat, List<PariSimple> parisGagnant) {
|
||||
// Article 5a: Cas d'arrivée normale - Calcul du rapport "gagnant"
|
||||
if (resultat.getChevauxPremiers().isEmpty()) {
|
||||
gains.setRapportGagnant(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Gestion des écuries (Article 2)
|
||||
Long chevalGagnantId = resultat.getChevauxPremiers().get(0);
|
||||
Cheval chevalGagnant = chevalRepository.findById(chevalGagnantId).orElse(null);
|
||||
|
||||
if (chevalGagnant == null) {
|
||||
gains.setRapportGagnant(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Trouver tous les chevaux de la même écurie
|
||||
List<Cheval> chevauxEcurie = Collections.singletonList(chevalGagnant);
|
||||
if (chevalGagnant.getNomEcurie() != null) {
|
||||
chevauxEcurie = chevalRepository.findByNomEcurie(chevalGagnant.getNomEcurie());
|
||||
}
|
||||
|
||||
// Calculer le total des mises sur les chevaux de l'écurie
|
||||
double totalMisesEcurie = 0.0;
|
||||
for (Cheval cheval : chevauxEcurie) {
|
||||
double misesCheval = parisGagnant.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(cheval.getId()))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
totalMisesEcurie += misesCheval;
|
||||
}
|
||||
|
||||
if (totalMisesEcurie > 0) {
|
||||
gains.setRapportGagnant(gains.getMasseGagnant() / totalMisesEcurie);
|
||||
} else {
|
||||
gains.setRapportGagnant(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculerRapportsGagnantDeadHeat(Gains gains, ResultatCourse resultat, List<PariSimple> parisGagnant) {
|
||||
// Article 5a: Cas d'arrivée "dead-heat" - Calcul des rapports "gagnant"
|
||||
if (resultat.getChevauxPremiers().isEmpty()) {
|
||||
gains.setRapportGagnant(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer le bénéfice à répartir (Article 5a)
|
||||
double totalMisesPayables = 0.0;
|
||||
for (Long chevalId : resultat.getChevauxPremiers()) {
|
||||
double misesCheval = parisGagnant.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
totalMisesPayables += misesCheval;
|
||||
}
|
||||
|
||||
double beneficeARepartir = gains.getMasseGagnant() - totalMisesPayables;
|
||||
|
||||
if (beneficeARepartir <= 0) {
|
||||
gains.setRapportGagnant(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Diviser le bénéfice en autant de parties qu'il y a de chevaux classés premiers
|
||||
double partParCheval = beneficeARepartir / resultat.getChevauxPremiers().size();
|
||||
|
||||
// Pour chaque cheval, calculer son rapport
|
||||
double rapportTotal = 0.0;
|
||||
int chevauxAvecMises = 0;
|
||||
|
||||
for (Long chevalId : resultat.getChevauxPremiers()) {
|
||||
double misesCheval = parisGagnant.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
|
||||
if (misesCheval > 0) {
|
||||
double rapportCheval = (partParCheval / misesCheval) + 1;
|
||||
rapportTotal += rapportCheval;
|
||||
chevauxAvecMises++;
|
||||
}
|
||||
}
|
||||
|
||||
// Article 6: Si aucun mise sur un cheval, partager entre les autres
|
||||
if (chevauxAvecMises > 0) {
|
||||
gains.setRapportGagnant(rapportTotal / chevauxAvecMises);
|
||||
} else {
|
||||
gains.setRapportGagnant(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculerRapportsPlaceNormal(Gains gains, ResultatCourse resultat, List<PariSimple> parisPlace) {
|
||||
// Article 5b: Cas d'arrivée normale - Calcul du rapport "placé"
|
||||
int nombreChevauxInscrits = gains.getCourse().getNombreChevauxInscrits();
|
||||
List<Long> chevauxPayables = new ArrayList<>();
|
||||
|
||||
// Déterminer les chevaux payables selon le nombre de partants
|
||||
if (nombreChevauxInscrits >= 4 && nombreChevauxInscrits <= 7) {
|
||||
// 2 premiers
|
||||
chevauxPayables.addAll(resultat.getChevauxPremiers());
|
||||
chevauxPayables.addAll(resultat.getChevauxDeuxiemes());
|
||||
} else if (nombreChevauxInscrits >= 8) {
|
||||
// 3 premiers
|
||||
chevauxPayables.addAll(resultat.getChevauxPremiers());
|
||||
chevauxPayables.addAll(resultat.getChevauxDeuxiemes());
|
||||
chevauxPayables.addAll(resultat.getChevauxTroisiemes());
|
||||
}
|
||||
|
||||
if (chevauxPayables.isEmpty()) {
|
||||
gains.setRapportPlace(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer le bénéfice à répartir (Article 5b)
|
||||
double totalMisesPayables = 0.0;
|
||||
for (Long chevalId : chevauxPayables) {
|
||||
double misesCheval = parisPlace.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
totalMisesPayables += misesCheval;
|
||||
}
|
||||
|
||||
double beneficeARepartir = gains.getMassePlace() - totalMisesPayables;
|
||||
|
||||
if (beneficeARepartir <= 0) {
|
||||
gains.setRapportPlace(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Diviser le bénéfice en autant de parties qu'il y a de chevaux payables
|
||||
double partParCheval = beneficeARepartir / chevauxPayables.size();
|
||||
|
||||
// Pour chaque cheval, calculer son rapport
|
||||
double rapportTotal = 0.0;
|
||||
int chevauxAvecMises = 0;
|
||||
|
||||
for (Long chevalId : chevauxPayables) {
|
||||
double misesCheval = parisPlace.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
|
||||
if (misesCheval > 0) {
|
||||
double rapportCheval = (partParCheval / misesCheval) + 1;
|
||||
rapportTotal += rapportCheval;
|
||||
chevauxAvecMises++;
|
||||
}
|
||||
}
|
||||
|
||||
// Article 6: Si aucun mise sur un cheval, partager entre les autres
|
||||
if (chevauxAvecMises > 0) {
|
||||
gains.setRapportPlace(rapportTotal / chevauxAvecMises);
|
||||
} else {
|
||||
gains.setRapportPlace(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculerRapportsPlaceDeadHeat(Gains gains, ResultatCourse resultat, List<PariSimple> parisPlace) {
|
||||
// Article 5b et 5c: Cas d'arrivée "dead-heat" - Calcul des rapports "placé"
|
||||
int nombreChevauxInscrits = gains.getCourse().getNombreChevauxInscrits();
|
||||
|
||||
// Déterminer les chevaux payables selon le nombre de partants et le type de dead-heat
|
||||
List<Long> chevauxPayables = determinerChevauxPayablesPlace(resultat, nombreChevauxInscrits);
|
||||
|
||||
if (chevauxPayables.isEmpty()) {
|
||||
gains.setRapportPlace(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer le bénéfice à répartir
|
||||
double totalMisesPayables = 0.0;
|
||||
for (Long chevalId : chevauxPayables) {
|
||||
double misesCheval = parisPlace.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
totalMisesPayables += misesCheval;
|
||||
}
|
||||
|
||||
double beneficeARepartir = gains.getMassePlace() - totalMisesPayables;
|
||||
|
||||
if (beneficeARepartir <= 0) {
|
||||
gains.setRapportPlace(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Application des règles spécifiques pour les dead-heats (Article 5c)
|
||||
if (nombreChevauxInscrits < 8) {
|
||||
beneficeARepartir = appliquerReglesPlaceMoinsHuit(resultat, beneficeARepartir);
|
||||
} else {
|
||||
beneficeARepartir = appliquerReglesPlaceHuitPlus(resultat, beneficeARepartir);
|
||||
}
|
||||
|
||||
// Diviser le bénéfice entre les chevaux payables
|
||||
double partParCheval = beneficeARepartir / chevauxPayables.size();
|
||||
|
||||
// Pour chaque cheval, calculer son rapport
|
||||
double rapportTotal = 0.0;
|
||||
int chevauxAvecMises = 0;
|
||||
|
||||
for (Long chevalId : chevauxPayables) {
|
||||
double misesCheval = parisPlace.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
|
||||
if (misesCheval > 0) {
|
||||
double rapportCheval = (partParCheval / misesCheval) + 1;
|
||||
rapportTotal += rapportCheval;
|
||||
chevauxAvecMises++;
|
||||
}
|
||||
}
|
||||
|
||||
// Article 6: Si aucun mise sur un cheval, partager entre les autres
|
||||
if (chevauxAvecMises > 0) {
|
||||
gains.setRapportPlace(rapportTotal / chevauxAvecMises);
|
||||
} else {
|
||||
gains.setRapportPlace(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Long> determinerChevauxPayablesPlace(ResultatCourse resultat, int nombreChevauxInscrits) {
|
||||
List<Long> chevauxPayables = new ArrayList<>();
|
||||
|
||||
// Article 3: Déterminer les chevaux payables selon le nombre de partants
|
||||
if (nombreChevauxInscrits >= 4 && nombreChevauxInscrits <= 7) {
|
||||
// 2 premiers
|
||||
chevauxPayables.addAll(resultat.getChevauxPremiers());
|
||||
chevauxPayables.addAll(resultat.getChevauxDeuxiemes());
|
||||
} else if (nombreChevauxInscrits >= 8) {
|
||||
// 3 premiers
|
||||
chevauxPayables.addAll(resultat.getChevauxPremiers());
|
||||
chevauxPayables.addAll(resultat.getChevauxDeuxiemes());
|
||||
chevauxPayables.addAll(resultat.getChevauxTroisiemes());
|
||||
}
|
||||
|
||||
return chevauxPayables;
|
||||
}
|
||||
|
||||
private double appliquerReglesPlaceMoinsHuit(ResultatCourse resultat, double beneficeARepartir) {
|
||||
// Article 5c: Règles pour les courses de moins de 8 chevaux avec dead-heat
|
||||
int nbPremiers = resultat.getChevauxPremiers().size();
|
||||
int nbDeuxiemes = resultat.getChevauxDeuxiemes().size();
|
||||
|
||||
if (nbPremiers > 1) {
|
||||
// Plus d'un cheval classé premier
|
||||
return beneficeARepartir / nbPremiers;
|
||||
} else if (nbDeuxiemes > 1) {
|
||||
// Plus d'un cheval classé deuxième
|
||||
double partPremier = beneficeARepartir / 2;
|
||||
double partDeuxiemes = beneficeARepartir / 2;
|
||||
return partPremier + (partDeuxiemes / nbDeuxiemes);
|
||||
}
|
||||
|
||||
return beneficeARepartir;
|
||||
}
|
||||
|
||||
private double appliquerReglesPlaceHuitPlus(ResultatCourse resultat, double beneficeARepartir) {
|
||||
// Article 5c: Règles pour les courses de 8 chevaux et plus avec dead-heat
|
||||
int nbPremiers = resultat.getChevauxPremiers().size();
|
||||
int nbDeuxiemes = resultat.getChevauxDeuxiemes().size();
|
||||
int nbTroisiemes = resultat.getChevauxTroisiemes().size();
|
||||
|
||||
if (nbPremiers == 1 && nbDeuxiemes == 1) {
|
||||
// Un seul premier et un seul deuxième, plusieurs troisièmes
|
||||
double partPremier = beneficeARepartir / 3;
|
||||
double partDeuxieme = beneficeARepartir / 3;
|
||||
double partTroisiemes = beneficeARepartir / 3;
|
||||
return partPremier + partDeuxieme + (partTroisiemes / nbTroisiemes);
|
||||
} else if (nbPremiers == 1 && nbDeuxiemes > 1) {
|
||||
// Un seul premier, plusieurs deuxièmes
|
||||
double partPremier = beneficeARepartir / 3;
|
||||
double partDeuxiemes = beneficeARepartir * 2 / 3;
|
||||
return partPremier + (partDeuxiemes / nbDeuxiemes);
|
||||
} else if (nbPremiers == 2) {
|
||||
// Deux chevaux classés premiers
|
||||
double partParPremier = beneficeARepartir / 3;
|
||||
double partTroisiemes = beneficeARepartir / 3;
|
||||
return (2 * partParPremier) + (partTroisiemes / nbTroisiemes);
|
||||
} else if (nbPremiers > 2) {
|
||||
// Plus de deux chevaux classés premiers
|
||||
return beneficeARepartir / nbPremiers;
|
||||
}
|
||||
|
||||
return beneficeARepartir;
|
||||
}
|
||||
|
||||
private void gererCagnotte(Gains gains, ResultatCourse resultat,
|
||||
List<PariSimple> parisGagnant, List<PariSimple> parisPlace) {
|
||||
// Article 6 et 7: Gestion de la cagnotte (tirelire)
|
||||
double montantCagnotte = 0.0;
|
||||
|
||||
// Vérifier les conditions pour la cagnotte GAGNANT
|
||||
boolean cagnotteGagnant = false;
|
||||
if (resultat.getChevauxPremiers().isEmpty()) {
|
||||
// Aucun cheval classé premier
|
||||
cagnotteGagnant = true;
|
||||
} else {
|
||||
// Vérifier s'il n'y a aucune mise sur les chevaux premiers
|
||||
boolean aucuneMiseGagnant = true;
|
||||
for (Long chevalId : resultat.getChevauxPremiers()) {
|
||||
double mises = parisGagnant.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
if (mises > 0) {
|
||||
aucuneMiseGagnant = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cagnotteGagnant = aucuneMiseGagnant;
|
||||
}
|
||||
|
||||
if (cagnotteGagnant) {
|
||||
montantCagnotte += gains.getMasseGagnant();
|
||||
gains.setMasseGagnant(0.0);
|
||||
gains.setRapportGagnant(0.0);
|
||||
}
|
||||
|
||||
// Vérifier les conditions pour la cagnotte PLACE
|
||||
boolean cagnottePlace = false;
|
||||
int nombreChevauxInscrits = gains.getCourse().getNombreChevauxInscrits();
|
||||
|
||||
// Article 6.3: Si moins de 4 chevaux ont pris part à la course
|
||||
if (nombreChevauxInscrits < 4) {
|
||||
cagnottePlace = true;
|
||||
} else {
|
||||
List<Long> chevauxPayables = determinerChevauxPayablesPlace(resultat, nombreChevauxInscrits);
|
||||
|
||||
if (chevauxPayables.isEmpty()) {
|
||||
cagnottePlace = true;
|
||||
} else {
|
||||
// Vérifier s'il n'y a aucune mise sur les chevaux payables
|
||||
boolean aucuneMisePlace = true;
|
||||
for (Long chevalId : chevauxPayables) {
|
||||
double mises = parisPlace.stream()
|
||||
.filter(p -> p.getCheval().getId().equals(chevalId))
|
||||
.mapToDouble(PariSimple::getMise)
|
||||
.sum();
|
||||
if (mises > 0) {
|
||||
aucuneMisePlace = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cagnottePlace = aucuneMisePlace;
|
||||
}
|
||||
}
|
||||
|
||||
if (cagnottePlace) {
|
||||
montantCagnotte += gains.getMassePlace();
|
||||
gains.setMassePlace(0.0);
|
||||
gains.setRapportPlace(0.0);
|
||||
}
|
||||
|
||||
gains.setMontantCagnotte(montantCagnotte);
|
||||
|
||||
// TODO: Implémenter la logique de report de la cagnotte selon l'article 7
|
||||
}
|
||||
|
||||
public Gains obtenirGainsParCourse(Long courseId) {
|
||||
return gainsRepository.findByCourseId(courseId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.pmumali.ch1_simple.service;
|
||||
|
||||
import com.pmumali.ch1_simple.model.Course;
|
||||
import com.pmumali.ch1_simple.model.PariSimple;
|
||||
import com.pmumali.ch1_simple.repository.ChevalRepository;
|
||||
import com.pmumali.ch1_simple.repository.CourseRepository;
|
||||
import com.pmumali.ch1_simple.repository.PariSimpleRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class PariSimpleService {
|
||||
|
||||
@Autowired
|
||||
private PariSimpleRepository pariRepository;
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
private static final double MISE_DE_BASE = 500.0;
|
||||
|
||||
public PariSimple placerPari(PariSimple pari) {
|
||||
// Vérifier que le type de pari est valide pour la course
|
||||
Course course = pari.getCourse();
|
||||
if ("GAGNANT".equals(pari.getTypePari()) && course.getNombreChevauxInscrits() < 2) {
|
||||
throw new IllegalArgumentException("Pari GAGNANT impossible: moins de 2 chevaux");
|
||||
}
|
||||
|
||||
if ("PLACE".equals(pari.getTypePari()) && course.getNombreChevauxInscrits() < 3) {
|
||||
throw new IllegalArgumentException("Pari PLACE impossible: moins de 3 chevaux");
|
||||
}
|
||||
|
||||
return pariRepository.save(pari);
|
||||
}
|
||||
|
||||
public List<PariSimple> obtenirParisParCourse(Long courseId) {
|
||||
return pariRepository.findByCourseId(courseId);
|
||||
}
|
||||
|
||||
public List<PariSimple> obtenirParisParCourseEtType(Long courseId, String typePari) {
|
||||
return pariRepository.findByCourseIdAndTypePari(courseId, typePari);
|
||||
}
|
||||
|
||||
public List<PariSimple> obtenirParisParCheval(Long chevalId) {
|
||||
return pariRepository.findByChevalId(chevalId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.pmumali.ch2_jumelegagnant.controller;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.Cheval;
|
||||
import com.pmumali.ch2_jumelegagnant.service.ChevalService;
|
||||
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/chevaux")
|
||||
public class ChevalController {
|
||||
|
||||
@Autowired
|
||||
private ChevalService chevalService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Cheval> ajouterCheval(@RequestBody Cheval cheval) {
|
||||
return ResponseEntity.ok(chevalService.ajouterCheval(cheval));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}")
|
||||
public ResponseEntity<List<Cheval>> obtenirChevauxParCourse(@PathVariable Long courseId) {
|
||||
return ResponseEntity.ok(chevalService.obtenirChevauxParCourse(courseId));
|
||||
}
|
||||
|
||||
@GetMapping("/non-partants")
|
||||
public ResponseEntity<List<Cheval>> obtenirChevauxNonPartants() {
|
||||
return ResponseEntity.ok(chevalService.obtenirChevauxNonPartants());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.pmumali.ch2_jumelegagnant.controller;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.Course;
|
||||
import com.pmumali.ch2_jumelegagnant.service.CourseService;
|
||||
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 CourseController {
|
||||
|
||||
@Autowired
|
||||
private CourseService courseService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Course> creerCourse(@RequestBody Course course) {
|
||||
return ResponseEntity.ok(courseService.creerCourse(course));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Course>> obtenirToutesCourses() {
|
||||
return ResponseEntity.ok(courseService.obtenirToutesCourses());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Course> obtenirCourseParId(@PathVariable Long id) {
|
||||
Course course = courseService.obtenirCourseParId(id);
|
||||
return course != null ? ResponseEntity.ok(course) : ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@GetMapping("/terminees")
|
||||
public ResponseEntity<List<Course>> obtenirCoursesTerminees() {
|
||||
return ResponseEntity.ok(courseService.obtenirCoursesTerminees());
|
||||
}
|
||||
|
||||
@GetMapping("/avenir")
|
||||
public ResponseEntity<List<Course>> obtenirCoursesAVenir() {
|
||||
return ResponseEntity.ok(courseService.obtenirCoursesAVenir());
|
||||
}
|
||||
|
||||
@GetMapping("/annulees")
|
||||
public ResponseEntity<List<Course>> obtenirCoursesAnnulees() {
|
||||
return ResponseEntity.ok(courseService.obtenirCoursesAnnulees());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.pmumali.ch2_jumelegagnant.controller;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.Gains;
|
||||
import com.pmumali.ch2_jumelegagnant.model.ResultatCourse;
|
||||
import com.pmumali.ch2_jumelegagnant.service.GainsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/gains")
|
||||
public class GainsController {
|
||||
|
||||
@Autowired
|
||||
private GainsService gainsService;
|
||||
|
||||
@PostMapping("/calculer/{courseId}")
|
||||
public ResponseEntity<Gains> calculerGains(@PathVariable Long courseId, @RequestBody ResultatCourse resultat) {
|
||||
return ResponseEntity.ok(gainsService.calculerGains(courseId, resultat));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}")
|
||||
public ResponseEntity<Gains> obtenirGainsParCourse(@PathVariable Long courseId) {
|
||||
Gains gains = gainsService.obtenirGainsParCourse(courseId);
|
||||
return gains != null ? ResponseEntity.ok(gains) : ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.pmumali.ch2_jumelegagnant.controller;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.PariJumeleGagnant;
|
||||
import com.pmumali.ch2_jumelegagnant.service.PariJumeleService;
|
||||
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-jumele")
|
||||
public class PariJumeleController {
|
||||
|
||||
@Autowired
|
||||
private PariJumeleService pariService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<PariJumeleGagnant> placerPari(@RequestBody PariJumeleGagnant pari) {
|
||||
return ResponseEntity.ok(pariService.placerPari(pari));
|
||||
}
|
||||
|
||||
@GetMapping("/course/{courseId}")
|
||||
public ResponseEntity<List<PariJumeleGagnant>> obtenirParisParCourse(@PathVariable Long courseId) {
|
||||
return ResponseEntity.ok(pariService.obtenirParisParCourse(courseId));
|
||||
}
|
||||
|
||||
@GetMapping("/cheval/{chevalId}")
|
||||
public ResponseEntity<List<PariJumeleGagnant>> obtenirParisParCheval(@PathVariable Long chevalId) {
|
||||
return ResponseEntity.ok(pariService.obtenirParisParCheval(chevalId));
|
||||
}
|
||||
|
||||
@GetMapping("/calculer-formule-combinee/{nombreChevaux}")
|
||||
public ResponseEntity<Double> calculerCoutFormuleCombinee(@PathVariable int nombreChevaux) {
|
||||
return ResponseEntity.ok(pariService.calculerCoutFormuleCombinee(nombreChevaux));
|
||||
}
|
||||
|
||||
@GetMapping("/calculer-formule-champ")
|
||||
public ResponseEntity<Double> calculerCoutFormuleChamp(
|
||||
@RequestParam int nombreChevauxPartants,
|
||||
@RequestParam int nombreChevauxSelectionnes,
|
||||
@RequestParam boolean estChampTotal) {
|
||||
return ResponseEntity.ok(pariService.calculerCoutFormuleChamp(
|
||||
nombreChevauxPartants, nombreChevauxSelectionnes, estChampTotal));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.pmumali.ch2_jumelegagnant.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChevalDto {
|
||||
private Long id;
|
||||
private String nom;
|
||||
private int numero;
|
||||
private boolean estNonPartant;
|
||||
private Long courseId;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.pmumali.ch2_jumelegagnant.dto;
|
||||
|
||||
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 int nombreChevauxInscrits;
|
||||
private boolean estTerminee;
|
||||
private boolean estAnnulee;
|
||||
private List<Long> chevauxIds;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.pmumali.ch2_jumelegagnant.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GainsDto {
|
||||
private Long id;
|
||||
private Long courseId;
|
||||
private double masseAPartager;
|
||||
private double montantCagnotte;
|
||||
private List<Double> rapports;
|
||||
private LocalDateTime dateCalcul;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.pmumali.ch2_jumelegagnant.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class PariJumeleDto {
|
||||
private Long id;
|
||||
private String typePari;
|
||||
private double mise;
|
||||
private LocalDateTime datePari;
|
||||
private Long cheval1Id;
|
||||
private Long cheval2Id;
|
||||
private Long courseId;
|
||||
private boolean estPaye;
|
||||
private boolean estRembourse;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.pmumali.ch2_jumelegagnant.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ResultatCourseDto {
|
||||
private Long id;
|
||||
private Long courseId;
|
||||
private List<Long> chevauxPremiers;
|
||||
private List<Long> chevauxDeuxiemes;
|
||||
private boolean aDeadHeat;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.pmumali.ch2_jumelegagnant.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;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.pmumali.ch2_jumelegagnant.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
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 int nombreChevauxInscrits;
|
||||
private boolean estTerminee;
|
||||
private boolean estAnnulee;
|
||||
|
||||
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
|
||||
private List<Cheval> chevaux;
|
||||
|
||||
@OneToOne(mappedBy = "course", cascade = CascadeType.ALL)
|
||||
private ResultatCourse resultat;
|
||||
}
|
||||
30
src/main/java/com/pmumali/ch2_jumelegagnant/model/Gains.java
Normal file
30
src/main/java/com/pmumali/ch2_jumelegagnant/model/Gains.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.pmumali.ch2_jumelegagnant.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@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 masseAPartager;
|
||||
private double montantCagnotte;
|
||||
|
||||
@ElementCollection
|
||||
private List<Double> rapports; // Rapports pour chaque combinaison payable
|
||||
|
||||
private LocalDateTime dateCalcul;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pmumali.ch2_jumelegagnant.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "pari_jumele_gagnant")
|
||||
public class PariJumeleGagnant {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String typePari; // UNITAIRE, COMBINE, CHAMP_TOTAL, CHAMP_PARTIEL
|
||||
private double mise = 500.0;
|
||||
private LocalDateTime datePari;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "cheval1_id")
|
||||
private Cheval cheval1;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "cheval2_id")
|
||||
private Cheval cheval2;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "course_id")
|
||||
private Course course;
|
||||
|
||||
private boolean estPaye;
|
||||
private boolean estRembourse;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.pmumali.ch2_jumelegagnant.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "resultat_course")
|
||||
public class ResultatCourse {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "course_id")
|
||||
private Course course;
|
||||
|
||||
@ElementCollection
|
||||
private List<Long> chevauxPremiers; // Pour gérer les dead-heats
|
||||
|
||||
@ElementCollection
|
||||
private List<Long> chevauxDeuxiemes; // Pour gérer les dead-heats
|
||||
|
||||
private boolean aDeadHeat;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.pmumali.ch2_jumelegagnant.repository;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.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);
|
||||
List<Cheval> findByEstNonPartant(boolean estNonPartant);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.pmumali.ch2_jumelegagnant.repository;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.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);
|
||||
List<Course> findByEstAnnulee(boolean estAnnulee);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.pmumali.ch2_jumelegagnant.repository;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.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);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.pmumali.ch2_jumelegagnant.repository;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.PariJumeleGagnant;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface PariJumeleRepository extends JpaRepository<PariJumeleGagnant, Long> {
|
||||
List<PariJumeleGagnant> findByCourseId(Long courseId);
|
||||
List<PariJumeleGagnant> findByCheval1IdOrCheval2Id(Long cheval1Id, Long cheval2Id);
|
||||
List<PariJumeleGagnant> findByEstRembourse(boolean estRembourse);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.pmumali.ch2_jumelegagnant.repository;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.ResultatCourse;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ResultatCourseRepository extends JpaRepository<ResultatCourse, Long> {
|
||||
ResultatCourse findByCourseId(Long courseId);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.pmumali.ch2_jumelegagnant.service;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.Cheval;
|
||||
import com.pmumali.ch2_jumelegagnant.repository.ChevalRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ChevalService {
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
public Cheval ajouterCheval(Cheval cheval) {
|
||||
return chevalRepository.save(cheval);
|
||||
}
|
||||
|
||||
public List<Cheval> obtenirChevauxParCourse(Long courseId) {
|
||||
return chevalRepository.findByCourseId(courseId);
|
||||
}
|
||||
|
||||
public List<Cheval> obtenirChevauxNonPartants() {
|
||||
return chevalRepository.findByEstNonPartant(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.pmumali.ch2_jumelegagnant.service;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.Course;
|
||||
import com.pmumali.ch2_jumelegagnant.repository.CourseRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Service
|
||||
public class CourseService {
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
public Course creerCourse(Course course) {
|
||||
return courseRepository.save(course);
|
||||
}
|
||||
|
||||
public List<Course> obtenirToutesCourses() {
|
||||
return courseRepository.findAll();
|
||||
}
|
||||
|
||||
public Course obtenirCourseParId(Long id) {
|
||||
return courseRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public List<Course> obtenirCoursesTerminees() {
|
||||
return courseRepository.findByEstTerminee(true);
|
||||
}
|
||||
|
||||
public List<Course> obtenirCoursesAVenir() {
|
||||
return courseRepository.findByEstTerminee(false);
|
||||
}
|
||||
|
||||
public List<Course> obtenirCoursesAnnulees() {
|
||||
return courseRepository.findByEstAnnulee(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
package com.pmumali.ch2_jumelegagnant.service;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.Course;
|
||||
import com.pmumali.ch2_jumelegagnant.model.Gains;
|
||||
import com.pmumali.ch2_jumelegagnant.model.PariJumeleGagnant;
|
||||
import com.pmumali.ch2_jumelegagnant.model.ResultatCourse;
|
||||
import com.pmumali.ch2_jumelegagnant.repository.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class GainsService {
|
||||
|
||||
@Autowired
|
||||
private PariJumeleRepository pariRepository;
|
||||
|
||||
@Autowired
|
||||
private GainsRepository gainsRepository;
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
@Autowired
|
||||
private ResultatCourseRepository resultatRepository;
|
||||
|
||||
private static final double RAPPORT_MINIMUM = 1.1;
|
||||
private static final double PRELEVEMENTS = 0.15; // 15% de prélèvements
|
||||
|
||||
public Gains calculerGains(Long courseId, ResultatCourse resultat) {
|
||||
Course course = courseRepository.findById(courseId)
|
||||
.orElseThrow(() -> new RuntimeException("Course non trouvée"));
|
||||
|
||||
List<PariJumeleGagnant> tousParis = pariRepository.findByCourseId(courseId);
|
||||
|
||||
// Calcul de la recette nette (Article 5)
|
||||
double totalMises = tousParis.stream().mapToDouble(PariJumeleGagnant::getMise).sum();
|
||||
double recetteNette = totalMises;
|
||||
|
||||
// Calcul des remboursements (Article 4)
|
||||
double montantRemboursements = calculerRemboursements(tousParis);
|
||||
|
||||
// Masse à partager (Article 5)
|
||||
double masseAPartager = recetteNette - montantRemboursements - (recetteNette * PRELEVEMENTS);
|
||||
|
||||
// Déterminer les combinaisons payables
|
||||
List<CombinaisonPayable> combinaisonsPayables = determinerCombinaisonsPayables(resultat);
|
||||
|
||||
// Calcul des rapports
|
||||
Gains gains = new Gains();
|
||||
gains.setCourse(course);
|
||||
gains.setMasseAPartager(masseAPartager);
|
||||
gains.setDateCalcul(LocalDateTime.now());
|
||||
|
||||
// Calcul détaillé des gains selon les règles
|
||||
if (resultat.isADeadHeat()) {
|
||||
calculerRapportsDeadHeat(gains, combinaisonsPayables, tousParis);
|
||||
} else {
|
||||
calculerRapportsNormaux(gains, combinaisonsPayables, tousParis);
|
||||
}
|
||||
|
||||
// Gérer la cagnotte si nécessaire (Article 8 et 9)
|
||||
gererCagnotte(gains, combinaisonsPayables);
|
||||
|
||||
return gainsRepository.save(gains);
|
||||
}
|
||||
|
||||
private double calculerRemboursements(List<PariJumeleGagnant> paris) {
|
||||
double remboursements = 0.0;
|
||||
|
||||
for (PariJumeleGagnant pari : paris) {
|
||||
// Article 4: Remboursement si un ou deux chevaux non partants
|
||||
if (pari.getCheval1().isEstNonPartant() || pari.getCheval2().isEstNonPartant()) {
|
||||
remboursements += pari.getMise();
|
||||
pari.setEstRembourse(true);
|
||||
pariRepository.save(pari);
|
||||
}
|
||||
}
|
||||
|
||||
return remboursements;
|
||||
}
|
||||
|
||||
private List<CombinaisonPayable> determinerCombinaisonsPayables(ResultatCourse resultat) {
|
||||
List<CombinaisonPayable> combinaisons = new ArrayList<>();
|
||||
|
||||
// Article 1: Le pari est payable si les deux chevaux occupent les deux premières places
|
||||
// Article 3: Gestion des dead-heats
|
||||
|
||||
if (resultat.isADeadHeat()) {
|
||||
// Cas de dead-heat
|
||||
if (resultat.getChevauxPremiers().size() >= 2) {
|
||||
// Article 3a: Dead-heat à la première place
|
||||
// Toutes les combinaisons des chevaux classés premiers pris deux à deux
|
||||
for (int i = 0; i < resultat.getChevauxPremiers().size(); i++) {
|
||||
for (int j = i + 1; j < resultat.getChevauxPremiers().size(); j++) {
|
||||
combinaisons.add(new CombinaisonPayable(
|
||||
resultat.getChevauxPremiers().get(i),
|
||||
resultat.getChevauxPremiers().get(j)
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if (resultat.getChevauxDeuxiemes().size() >= 2) {
|
||||
// Article 3b: Dead-heat à la deuxième place
|
||||
// Combinaisons du premier avec chaque deuxième
|
||||
for (Long chevalPremier : resultat.getChevauxPremiers()) {
|
||||
for (Long chevalDeuxieme : resultat.getChevauxDeuxiemes()) {
|
||||
combinaisons.add(new CombinaisonPayable(chevalPremier, chevalDeuxieme));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Cas normal
|
||||
if (!resultat.getChevauxPremiers().isEmpty() && !resultat.getChevauxDeuxiemes().isEmpty()) {
|
||||
combinaisons.add(new CombinaisonPayable(
|
||||
resultat.getChevauxPremiers().get(0),
|
||||
resultat.getChevauxDeuxiemes().get(0)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
private void calculerRapportsNormaux(Gains gains, List<CombinaisonPayable> combinaisonsPayables,
|
||||
List<PariJumeleGagnant> tousParis) {
|
||||
// Article 5a: Cas d'arrivée normale
|
||||
if (combinaisonsPayables.isEmpty()) {
|
||||
gains.setMontantCagnotte(gains.getMasseAPartager());
|
||||
gains.setMasseAPartager(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalement, une seule combinaison payable en cas d'arrivée normale
|
||||
CombinaisonPayable combinaisonPayable = combinaisonsPayables.get(0);
|
||||
|
||||
// Calculer le total des mises sur cette combinaison
|
||||
double totalMises = calculerMisesSurCombinaison(combinaisonPayable, tousParis);
|
||||
|
||||
if (totalMises > 0) {
|
||||
double rapport = gains.getMasseAPartager() / totalMises;
|
||||
gains.setRapports(Collections.singletonList(Math.max(rapport, RAPPORT_MINIMUM)));
|
||||
} else {
|
||||
// Article 8a: Aucune mise sur la combinaison payable
|
||||
gains.setMontantCagnotte(gains.getMasseAPartager());
|
||||
gains.setMasseAPartager(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculerRapportsDeadHeat(Gains gains, List<CombinaisonPayable> combinaisonsPayables,
|
||||
List<PariJumeleGagnant> tousParis) {
|
||||
// Article 5b: Cas d'arrivée "dead heat"
|
||||
if (combinaisonsPayables.isEmpty()) {
|
||||
gains.setMontantCagnotte(gains.getMasseAPartager());
|
||||
gains.setMasseAPartager(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer le total des mises sur toutes les combinaisons payables
|
||||
double totalMisesPayables = 0.0;
|
||||
Map<CombinaisonPayable, Double> misesParCombinaison = new HashMap<>();
|
||||
|
||||
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
||||
double mises = calculerMisesSurCombinaison(combinaison, tousParis);
|
||||
misesParCombinaison.put(combinaison, mises);
|
||||
totalMisesPayables += mises;
|
||||
}
|
||||
|
||||
// Calculer le bénéfice à répartir
|
||||
double beneficeARepartir = gains.getMasseAPartager() - totalMisesPayables;
|
||||
|
||||
if (beneficeARepartir <= 0) {
|
||||
// Article 8b: Gestion des cas où il n'y a pas de bénéfice
|
||||
gererCasParticuliersDeadHeat(gains, combinaisonsPayables, misesParCombinaison);
|
||||
return;
|
||||
}
|
||||
|
||||
// Diviser le bénéfice en autant de parties qu'il y a de combinaisons payables
|
||||
double partParCombinaison = beneficeARepartir / combinaisonsPayables.size();
|
||||
|
||||
// Calculer les rapports pour chaque combinaison
|
||||
List<Double> rapports = new ArrayList<>();
|
||||
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
||||
double mises = misesParCombinaison.get(combinaison);
|
||||
if (mises > 0) {
|
||||
double rapport = (partParCombinaison / mises) + 1;
|
||||
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
||||
} else {
|
||||
// Article 8b1: Aucune mise sur une combinaison payable
|
||||
// Répartir le bénéfice entre les autres combinaisons
|
||||
double partSupplementaire = partParCombinaison / (combinaisonsPayables.size() - 1);
|
||||
// Nous ajouterons cette part aux autres combinaisons plus tard
|
||||
rapports.add(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Ajuster les rapports pour les combinaisons sans mises
|
||||
ajusterRapportsSansMises(rapports, combinaisonsPayables, misesParCombinaison, partParCombinaison);
|
||||
|
||||
gains.setRapports(rapports);
|
||||
}
|
||||
|
||||
private void gererCasParticuliersDeadHeat(Gains gains, List<CombinaisonPayable> combinaisonsPayables,
|
||||
Map<CombinaisonPayable, Double> misesParCombinaison) {
|
||||
// Article 8b: Gestion des cas particuliers de dead-heat
|
||||
boolean aucuneMise = true;
|
||||
for (Double mises : misesParCombinaison.values()) {
|
||||
if (mises > 0) {
|
||||
aucuneMise = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (aucuneMise) {
|
||||
// Article 8b2, 8b3, 8b4: Aucune mise sur aucune combinaison payable
|
||||
gains.setMontantCagnotte(gains.getMasseAPartager());
|
||||
gains.setMasseAPartager(0.0);
|
||||
} else {
|
||||
// Répartir la masse entre les combinaisons avec des mises
|
||||
double totalMises = misesParCombinaison.values().stream().mapToDouble(Double::doubleValue).sum();
|
||||
List<Double> rapports = new ArrayList<>();
|
||||
|
||||
for (CombinaisonPayable combinaison : combinaisonsPayables) {
|
||||
double mises = misesParCombinaison.get(combinaison);
|
||||
if (mises > 0) {
|
||||
double rapport = gains.getMasseAPartager() / totalMises;
|
||||
rapports.add(Math.max(rapport, RAPPORT_MINIMUM));
|
||||
} else {
|
||||
rapports.add(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
gains.setRapports(rapports);
|
||||
}
|
||||
}
|
||||
|
||||
private void ajusterRapportsSansMises(List<Double> rapports, List<CombinaisonPayable> combinaisonsPayables,
|
||||
Map<CombinaisonPayable, Double> misesParCombinaison, double partParCombinaison) {
|
||||
// Compter le nombre de combinaisons sans mises
|
||||
int combinaisonsSansMises = 0;
|
||||
for (Double mises : misesParCombinaison.values()) {
|
||||
if (mises == 0) {
|
||||
combinaisonsSansMises++;
|
||||
}
|
||||
}
|
||||
|
||||
if (combinaisonsSansMises > 0) {
|
||||
double partSupplementaire = partParCombinaison / (combinaisonsPayables.size() - combinaisonsSansMises);
|
||||
|
||||
for (int i = 0; i < rapports.size(); i++) {
|
||||
if (rapports.get(i) > 0) {
|
||||
rapports.set(i, rapports.get(i) + partSupplementaire / misesParCombinaison.get(combinaisonsPayables.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double calculerMisesSurCombinaison(CombinaisonPayable combinaison, List<PariJumeleGagnant> tousParis) {
|
||||
double totalMises = 0.0;
|
||||
|
||||
for (PariJumeleGagnant pari : tousParis) {
|
||||
if ((pari.getCheval1().getId().equals(combinaison.getCheval1Id()) &&
|
||||
pari.getCheval2().getId().equals(combinaison.getCheval2Id())) ||
|
||||
(pari.getCheval1().getId().equals(combinaison.getCheval2Id()) &&
|
||||
pari.getCheval2().getId().equals(combinaison.getCheval1Id()))) {
|
||||
totalMises += pari.getMise();
|
||||
}
|
||||
}
|
||||
|
||||
return totalMises;
|
||||
}
|
||||
|
||||
private void gererCagnotte(Gains gains, List<CombinaisonPayable> combinaisonsPayables) {
|
||||
// Article 8 et 9: Gestion de la cagnotte
|
||||
if (combinaisonsPayables.isEmpty()) {
|
||||
gains.setMontantCagnotte(gains.getMasseAPartager());
|
||||
gains.setMasseAPartager(0.0);
|
||||
}
|
||||
|
||||
// Article 8c: Course annulée ou reportée
|
||||
if (gains.getCourse().isEstAnnulee()) {
|
||||
gains.setMontantCagnotte(gains.getMasseAPartager());
|
||||
gains.setMasseAPartager(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Classe interne pour représenter une combinaison payable
|
||||
private static class CombinaisonPayable {
|
||||
private Long cheval1Id;
|
||||
private Long cheval2Id;
|
||||
|
||||
public CombinaisonPayable(Long cheval1Id, Long cheval2Id) {
|
||||
this.cheval1Id = cheval1Id;
|
||||
this.cheval2Id = cheval2Id;
|
||||
}
|
||||
|
||||
public Long getCheval1Id() { return cheval1Id; }
|
||||
public Long getCheval2Id() { return cheval2Id; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CombinaisonPayable that = (CombinaisonPayable) o;
|
||||
return (Objects.equals(cheval1Id, that.cheval1Id) &&
|
||||
Objects.equals(cheval2Id, that.cheval2Id)) ||
|
||||
(Objects.equals(cheval1Id, that.cheval2Id) &&
|
||||
Objects.equals(cheval2Id, that.cheval1Id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Hash code qui ne dépend pas de l'ordre des chevaux
|
||||
long id1 = Math.min(cheval1Id, cheval2Id);
|
||||
long id2 = Math.max(cheval1Id, cheval2Id);
|
||||
return Objects.hash(id1, id2);
|
||||
}
|
||||
}
|
||||
|
||||
public Gains obtenirGainsParCourse(Long courseId) {
|
||||
return gainsRepository.findByCourseId(courseId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.pmumali.ch2_jumelegagnant.service;
|
||||
|
||||
import com.pmumali.ch2_jumelegagnant.model.PariJumeleGagnant;
|
||||
import com.pmumali.ch2_jumelegagnant.repository.ChevalRepository;
|
||||
import com.pmumali.ch2_jumelegagnant.repository.CourseRepository;
|
||||
import com.pmumali.ch2_jumelegagnant.repository.PariJumeleRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class PariJumeleService {
|
||||
|
||||
@Autowired
|
||||
private PariJumeleRepository pariRepository;
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
private static final double MISE_DE_BASE = 500.0;
|
||||
private static final double MISE_MAXIMALE = 20 * MISE_DE_BASE;
|
||||
|
||||
public PariJumeleGagnant placerPari(PariJumeleGagnant 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
|
||||
}
|
||||
|
||||
return pariRepository.save(pari);
|
||||
}
|
||||
|
||||
public List<PariJumeleGagnant> obtenirParisParCourse(Long courseId) {
|
||||
return pariRepository.findByCourseId(courseId);
|
||||
}
|
||||
|
||||
public List<PariJumeleGagnant> obtenirParisParCheval(Long chevalId) {
|
||||
return pariRepository.findByCheval1IdOrCheval2Id(chevalId, chevalId);
|
||||
}
|
||||
|
||||
// Méthode pour calculer le coût d'une formule combinée (Article 6a)
|
||||
public double calculerCoutFormuleCombinee(int nombreChevauxSelectionnes) {
|
||||
if (nombreChevauxSelectionnes < 2) {
|
||||
return 0;
|
||||
}
|
||||
int nombreCombinaisons = (nombreChevauxSelectionnes * (nombreChevauxSelectionnes - 1)) / 2;
|
||||
return nombreCombinaisons * MISE_DE_BASE;
|
||||
}
|
||||
|
||||
// Méthode pour calculer le coût d'une formule champ (Article 6b)
|
||||
public double calculerCoutFormuleChamp(int nombreChevauxPartants, int nombreChevauxSelectionnes, boolean estChampTotal) {
|
||||
if (estChampTotal) {
|
||||
return (nombreChevauxPartants - 1) * MISE_DE_BASE;
|
||||
} else {
|
||||
return nombreChevauxSelectionnes * MISE_DE_BASE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.pmumali.simple.controller;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import com.pmumali.simple.model.Course;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CourseController {
|
||||
|
||||
private CourseDetailsDto convertToDetailsDto(Course course) {
|
||||
CourseDetailsDto dto = new CourseDetailsDto();
|
||||
dto.setId(course.getId());
|
||||
dto.setNom(course.getNom());
|
||||
dto.setDateCourse(course.getDateCourse());
|
||||
dto.setChevaux(course.getChevaux().stream()
|
||||
.map(this::convertChevalDto)
|
||||
.collect(Collectors.toList()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
private ChevalDto convertChevalDto(Cheval cheval) {
|
||||
ChevalDto dto = new ChevalDto();
|
||||
dto.setId(cheval.getId());
|
||||
dto.setNom(cheval.getNom());
|
||||
dto.setNonPartant(cheval.isEstNonPartant());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.pmumali.simple.controller;
|
||||
|
||||
import com.pmu.jumele.dto.PariRequest;
|
||||
import com.pmu.mali.model.ResultatCourse;
|
||||
import com.pmumali.simple.dto.PariResponse;
|
||||
import com.pmumali.simple.dto.ResultatCourseDto;
|
||||
import com.pmumali.simple.model.Pari;
|
||||
import com.pmumali.simple.service.PariService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/paris")
|
||||
public class PariController {
|
||||
|
||||
@Autowired
|
||||
private PariService pariService;
|
||||
|
||||
@PostMapping("/jumelé")
|
||||
public ResponseEntity<Pari> placerPari(
|
||||
@RequestBody PariRequest pariRequest) {
|
||||
|
||||
Pari pari = pariService.placerPariJumele(
|
||||
pariRequest.getClientId(),
|
||||
pariRequest.getCourseId(),
|
||||
pariRequest.getChevauxIds(),
|
||||
pariRequest.getMontantMise()
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(pari);
|
||||
}
|
||||
|
||||
@GetMapping("/resultats/{courseId}")
|
||||
public ResponseEntity<ResultatCourse> calculerResultats(
|
||||
@PathVariable Long courseId) {
|
||||
return ResponseEntity.ok(pariService.calculerResultats(courseId));
|
||||
}
|
||||
|
||||
@PostMapping("/jumele-place")
|
||||
public ResponseEntity<PariResponse> placerPariJumelePlace(
|
||||
@Valid @RequestBody PariRequest pariRequest) {
|
||||
|
||||
Pari pari = pariService.placerPariJumelePlace(pariRequest);
|
||||
return ResponseEntity.ok(convertToResponse(pari));
|
||||
}
|
||||
|
||||
@GetMapping("/resultats/jumele-place/{courseId}")
|
||||
public ResponseEntity<ResultatCourseDto> getResultatsJumelePlace(
|
||||
@PathVariable Long courseId) {
|
||||
Pari
|
||||
ResultatCourseDto resultat = pariService.calculerResultatsJumelePlace(courseId);
|
||||
return ResponseEntity.ok(resultat);
|
||||
}
|
||||
|
||||
|
||||
private PariResponse convertToResponse(Pari pari) {
|
||||
PariResponse response = new PariResponse();
|
||||
response.setId(pari.getId());
|
||||
response.setMontantMise(pari.getMontantMise());
|
||||
response.setDatePari(pari.getDatePari());
|
||||
response.setChevaux(pari.getChevauxJumeles().stream()
|
||||
.map(Cheval::getNom)
|
||||
.collect(Collectors.toList()));
|
||||
response.setCourseNom(pari.getCourse().getNom());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.pmumali.simple.dto;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChevalDto {
|
||||
private Long id;
|
||||
private String nom;
|
||||
private int numero;
|
||||
private boolean estNonPartant;
|
||||
|
||||
// Constructeurs, Getters, Setters
|
||||
|
||||
public static ChevalDto fromEntity(Cheval cheval) {
|
||||
ChevalDto dto = new ChevalDto();
|
||||
dto.setId(cheval.getId());
|
||||
dto.setNom(cheval.getNom());
|
||||
dto.setNumero(cheval.getNumero());
|
||||
dto.setEstNonPartant(cheval.isEstNonPartant());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.pmumali.simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CombinaisonDto {
|
||||
private String cheval1;
|
||||
private String cheval2;
|
||||
private int nombreMises;
|
||||
|
||||
// Getters, setters
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.pmumali.simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PariRequest {
|
||||
private Long clientId;
|
||||
private Long courseId;
|
||||
private List<Long> chevauxIds;
|
||||
private double montantMise;
|
||||
|
||||
// Getters, setters
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
package com.pmumali.simple.dto;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import com.pmumali.simple.model.Pari;
|
||||
import com.pmumali.simple.model.enums.TypePari;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* DTO pour la réponse API des paris
|
||||
* Contient toutes les informations à exposer au client
|
||||
*/
|
||||
public class PariResponse {
|
||||
private Long id;
|
||||
private String numeroPari;
|
||||
private TypePari typePari;
|
||||
private double montantMise;
|
||||
|
||||
@JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss")
|
||||
private LocalDateTime datePari;
|
||||
|
||||
private String statut; // EN_COURS, GAGNANT, PERDANT, REMBOURSE
|
||||
private Double gainsPotentiels;
|
||||
private boolean estPaye;
|
||||
|
||||
// Informations course
|
||||
private Long courseId;
|
||||
private String courseNom;
|
||||
@JsonFormat(pattern = "dd/MM/yyyy HH:mm")
|
||||
private LocalDateTime dateCourse;
|
||||
|
||||
// Chevaux sélectionnés
|
||||
private List<ChevalDto> chevaux;
|
||||
|
||||
// Informations client (simplifiées)
|
||||
private String clientNomComplet;
|
||||
private String clientNumero;
|
||||
|
||||
// Constructeurs
|
||||
public PariResponse() {}
|
||||
|
||||
public PariResponse(Long id, String numeroPari, TypePari typePari, double montantMise,
|
||||
LocalDateTime datePari, String statut, Double gainsPotentiels,
|
||||
boolean estPaye, Long courseId, String courseNom,
|
||||
LocalDateTime dateCourse) {
|
||||
this.id = id;
|
||||
this.numeroPari = numeroPari;
|
||||
this.typePari = typePari;
|
||||
this.montantMise = montantMise;
|
||||
this.datePari = datePari;
|
||||
this.statut = statut;
|
||||
this.gainsPotentiels = gainsPotentiels;
|
||||
this.estPaye = estPaye;
|
||||
this.courseId = courseId;
|
||||
this.courseNom = courseNom;
|
||||
this.dateCourse = dateCourse;
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getNumeroPari() {
|
||||
return numeroPari;
|
||||
}
|
||||
|
||||
public void setNumeroPari(String numeroPari) {
|
||||
this.numeroPari = numeroPari;
|
||||
}
|
||||
|
||||
public TypePari getTypePari() {
|
||||
return typePari;
|
||||
}
|
||||
|
||||
public void setTypePari(TypePari typePari) {
|
||||
this.typePari = typePari;
|
||||
}
|
||||
|
||||
public double getMontantMise() {
|
||||
return montantMise;
|
||||
}
|
||||
|
||||
public void setMontantMise(double montantMise) {
|
||||
this.montantMise = montantMise;
|
||||
}
|
||||
|
||||
public LocalDateTime getDatePari() {
|
||||
return datePari;
|
||||
}
|
||||
|
||||
public void setDatePari(LocalDateTime datePari) {
|
||||
this.datePari = datePari;
|
||||
}
|
||||
|
||||
public String getStatut() {
|
||||
return statut;
|
||||
}
|
||||
|
||||
public void setStatut(String statut) {
|
||||
this.statut = statut;
|
||||
}
|
||||
|
||||
public Double getGainsPotentiels() {
|
||||
return gainsPotentiels;
|
||||
}
|
||||
|
||||
public void setGainsPotentiels(Double gainsPotentiels) {
|
||||
this.gainsPotentiels = gainsPotentiels;
|
||||
}
|
||||
|
||||
public boolean isEstPaye() {
|
||||
return estPaye;
|
||||
}
|
||||
|
||||
public void setEstPaye(boolean estPaye) {
|
||||
this.estPaye = estPaye;
|
||||
}
|
||||
|
||||
public Long getCourseId() {
|
||||
return courseId;
|
||||
}
|
||||
|
||||
public void setCourseId(Long courseId) {
|
||||
this.courseId = courseId;
|
||||
}
|
||||
|
||||
public String getCourseNom() {
|
||||
return courseNom;
|
||||
}
|
||||
|
||||
public void setCourseNom(String courseNom) {
|
||||
this.courseNom = courseNom;
|
||||
}
|
||||
|
||||
public LocalDateTime getDateCourse() {
|
||||
return dateCourse;
|
||||
}
|
||||
|
||||
public void setDateCourse(LocalDateTime dateCourse) {
|
||||
this.dateCourse = dateCourse;
|
||||
}
|
||||
|
||||
public List<ChevalDto> getChevaux() {
|
||||
return chevaux;
|
||||
}
|
||||
|
||||
public void setChevaux(List<ChevalDto> chevaux) {
|
||||
this.chevaux = chevaux;
|
||||
}
|
||||
|
||||
public String getClientNomComplet() {
|
||||
return clientNomComplet;
|
||||
}
|
||||
|
||||
public void setClientNomComplet(String clientNomComplet) {
|
||||
this.clientNomComplet = clientNomComplet;
|
||||
}
|
||||
|
||||
public String getClientNumero() {
|
||||
return clientNumero;
|
||||
}
|
||||
|
||||
public void setClientNumero(String clientNumero) {
|
||||
this.clientNumero = clientNumero;
|
||||
}
|
||||
|
||||
// Méthode utilitaire de conversion depuis l'entité
|
||||
public static PariResponse fromEntity(Pari pari) {
|
||||
PariResponse response = new PariResponse(
|
||||
pari.getId(),
|
||||
"PMU-" + pari.getId(),
|
||||
pari.getTypePari(),
|
||||
pari.getMontantMise(),
|
||||
pari.getDatePari(),
|
||||
determinerStatut(pari),
|
||||
pari.getGains(),
|
||||
pari.isEstPaye(),
|
||||
pari.getCourse().getId(),
|
||||
pari.getCourse().getNom(),
|
||||
pari.getCourse().getDateCourse()
|
||||
);
|
||||
|
||||
response.setChevaux(pari.getChevauxJumeles().stream()
|
||||
.map(ChevalDto::fromEntity)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
response.setClientNomComplet(pari.getClient().getPrenom() + " " + pari.getClient().getNom());
|
||||
response.setClientNumero(pari.getClient().getNumeroClient());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static String determinerStatut(Pari pari) {
|
||||
if (pari.getChevauxJumeles().stream().anyMatch(Cheval::isEstNonPartant)) {
|
||||
return "REMBOURSE";
|
||||
}
|
||||
if (pari.isEstPaye()) {
|
||||
return "GAGNANT";
|
||||
}
|
||||
if (pari.getCourse().isTerminee() && !pari.isEstPaye()) {
|
||||
return "PERDANT";
|
||||
}
|
||||
return "EN_COURS";
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.pmumali.simple.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ResultatCourseDto {
|
||||
private Long courseId;
|
||||
private List<CombinaisonDto> combinaisonsPayables;
|
||||
private Map<String, Double> rapports;
|
||||
private boolean tirelire;
|
||||
|
||||
// Getters, setters
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.pmumali.simple.exception;
|
||||
|
||||
|
||||
/**
|
||||
* Exception métier pour les erreurs spécifiques aux paris PMU Mali
|
||||
* conforme aux règles du "Jumelé Placé"
|
||||
*/
|
||||
public class PariException extends RuntimeException {
|
||||
|
||||
private final ErrorCode errorCode;
|
||||
private final String details;
|
||||
|
||||
// Codes d'erreur standardisés
|
||||
public enum ErrorCode {
|
||||
// Article 1 - Règles de base
|
||||
MISE_MINIMALE_NON_ATTEINTE("La mise minimale est de 500 FCFA"),
|
||||
|
||||
// Article 2 - Limitation des enjeux
|
||||
LIMITE_MISE_DEPASSEE("Limite de mise dépassée (20x500 FCFA maximum)"),
|
||||
|
||||
// Article 3 - Dead Heat
|
||||
CALCUL_RAPPORT_IMPOSSIBLE("Impossible de calculer les rapports pour ce Dead Heat"),
|
||||
|
||||
// Article 4 - Non-partants
|
||||
CHEVAL_NON_PARTANT("Pari invalide : cheval non-partant"),
|
||||
|
||||
// Article 5 - Calcul des rapports
|
||||
RAPPORT_INVALIDE("Rapport calculé invalide (<1.1)"),
|
||||
|
||||
// Article 6 - Formules
|
||||
FORMULE_INVALIDE("Formule de pari invalide"),
|
||||
|
||||
// Article 8 - Cas particuliers
|
||||
COURSE_ANNULEE("Course annulée - paris remboursés"),
|
||||
|
||||
// Validations générales
|
||||
PARI_INVALIDE("Pari invalide"),
|
||||
SOLDE_INSUFFISANT("Solde insuffisant pour placer ce pari"),
|
||||
CLIENT_BLOQUE("Compte client bloqué");
|
||||
|
||||
private final String message;
|
||||
|
||||
ErrorCode(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructeurs
|
||||
public PariException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.errorCode = errorCode;
|
||||
this.details = null;
|
||||
}
|
||||
|
||||
public PariException(ErrorCode errorCode, String details) {
|
||||
super(errorCode.getMessage() + " : " + details);
|
||||
this.errorCode = errorCode;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public PariException(ErrorCode errorCode, Throwable cause) {
|
||||
super(errorCode.getMessage(), cause);
|
||||
this.errorCode = errorCode;
|
||||
this.details = null;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public ErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
// Méthode utilitaire pour construire les messages
|
||||
public static String buildLimiteMiseMessage(double limite) {
|
||||
return String.format("Limite de mise dépassée (max %,.0f FCFA par course selon Article 2)", limite);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
public class Cheval {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String nom;
|
||||
private int numero;
|
||||
|
||||
private boolean estNonPartant;
|
||||
private Integer positionArrivee;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Course course;
|
||||
|
||||
|
||||
public int getNumero() {
|
||||
return numero;
|
||||
}
|
||||
|
||||
public void setNumero(int numero) {
|
||||
this.numero = numero;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getNom() {
|
||||
return nom;
|
||||
}
|
||||
|
||||
public void setNom(String nom) {
|
||||
this.nom = nom;
|
||||
}
|
||||
|
||||
public boolean isEstNonPartant() {
|
||||
return estNonPartant;
|
||||
}
|
||||
|
||||
public void setEstNonPartant(boolean estNonPartant) {
|
||||
this.estNonPartant = estNonPartant;
|
||||
}
|
||||
|
||||
public Integer getPositionArrivee() {
|
||||
return positionArrivee;
|
||||
}
|
||||
|
||||
public void setPositionArrivee(Integer positionArrivee) {
|
||||
this.positionArrivee = positionArrivee;
|
||||
}
|
||||
|
||||
public Course getCourse() {
|
||||
return course;
|
||||
}
|
||||
|
||||
public void setCourse(Course course) {
|
||||
this.course = course;
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "clients")
|
||||
public class Client {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String nom;
|
||||
|
||||
@Column(nullable = false, length = 50)
|
||||
private String prenom;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 30)
|
||||
private String numeroClient;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
private String telephone;
|
||||
|
||||
@Column(name = "date_naissance")
|
||||
private LocalDate dateNaissance;
|
||||
|
||||
@Column(nullable = false, length = 1)
|
||||
private String sexe; // M ou F
|
||||
|
||||
@Column(name = "piece_identite", nullable = false, length = 50)
|
||||
private String pieceIdentite; // Numéro de CNI/Passeport
|
||||
|
||||
@Column(name = "type_piece", length = 20)
|
||||
private String typePieceIdentite; // CNI, PASSEPORT, PERMIS
|
||||
|
||||
@Column(name = "adresse_postale", length = 100)
|
||||
private String adressePostale;
|
||||
|
||||
@Column(name = "code_postal", length = 10)
|
||||
private String codePostal;
|
||||
|
||||
@Column(length = 50)
|
||||
private String ville;
|
||||
|
||||
@Column(length = 50)
|
||||
private String pays = "Mali";
|
||||
|
||||
@Column(name = "date_inscription", nullable = false)
|
||||
private LocalDate dateInscription = LocalDate.now();
|
||||
|
||||
@Column(name = "est_verifie", nullable = false)
|
||||
private boolean estVerifie = false;
|
||||
|
||||
@Column(name = "est_bloque", nullable = false)
|
||||
private boolean estBloque = false;
|
||||
|
||||
@Column(name = "solde_compte", nullable = false)
|
||||
private double soldeCompte = 0.0;
|
||||
|
||||
@Column(name = "limite_mise", nullable = false)
|
||||
private double limiteMise = 10000.0; // 20 x 500 FCFA (Article 2)
|
||||
|
||||
@OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<Pari> paris = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
|
||||
private List<Transaction> transactions = new ArrayList<>();
|
||||
|
||||
// Constructeurs
|
||||
public Client() {
|
||||
}
|
||||
|
||||
public Client(String nom, String prenom, String numeroClient, String email, String telephone) {
|
||||
this.nom = nom;
|
||||
this.prenom = prenom;
|
||||
this.numeroClient = numeroClient;
|
||||
this.email = email;
|
||||
this.telephone = telephone;
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getNom() {
|
||||
return nom;
|
||||
}
|
||||
|
||||
public void setNom(String nom) {
|
||||
this.nom = nom;
|
||||
}
|
||||
|
||||
public String getPrenom() {
|
||||
return prenom;
|
||||
}
|
||||
|
||||
public void setPrenom(String prenom) {
|
||||
this.prenom = prenom;
|
||||
}
|
||||
|
||||
public String getNumeroClient() {
|
||||
return numeroClient;
|
||||
}
|
||||
|
||||
public void setNumeroClient(String numeroClient) {
|
||||
this.numeroClient = numeroClient;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getTelephone() {
|
||||
return telephone;
|
||||
}
|
||||
|
||||
public void setTelephone(String telephone) {
|
||||
this.telephone = telephone;
|
||||
}
|
||||
|
||||
public LocalDate getDateNaissance() {
|
||||
return dateNaissance;
|
||||
}
|
||||
|
||||
public void setDateNaissance(LocalDate dateNaissance) {
|
||||
this.dateNaissance = dateNaissance;
|
||||
}
|
||||
|
||||
public String getSexe() {
|
||||
return sexe;
|
||||
}
|
||||
|
||||
public void setSexe(String sexe) {
|
||||
this.sexe = sexe;
|
||||
}
|
||||
|
||||
public String getPieceIdentite() {
|
||||
return pieceIdentite;
|
||||
}
|
||||
|
||||
public void setPieceIdentite(String pieceIdentite) {
|
||||
this.pieceIdentite = pieceIdentite;
|
||||
}
|
||||
|
||||
public String getTypePieceIdentite() {
|
||||
return typePieceIdentite;
|
||||
}
|
||||
|
||||
public void setTypePieceIdentite(String typePieceIdentite) {
|
||||
this.typePieceIdentite = typePieceIdentite;
|
||||
}
|
||||
|
||||
public String getAdressePostale() {
|
||||
return adressePostale;
|
||||
}
|
||||
|
||||
public void setAdressePostale(String adressePostale) {
|
||||
this.adressePostale = adressePostale;
|
||||
}
|
||||
|
||||
public String getCodePostal() {
|
||||
return codePostal;
|
||||
}
|
||||
|
||||
public void setCodePostal(String codePostal) {
|
||||
this.codePostal = codePostal;
|
||||
}
|
||||
|
||||
public String getVille() {
|
||||
return ville;
|
||||
}
|
||||
|
||||
public void setVille(String ville) {
|
||||
this.ville = ville;
|
||||
}
|
||||
|
||||
public String getPays() {
|
||||
return pays;
|
||||
}
|
||||
|
||||
public void setPays(String pays) {
|
||||
this.pays = pays;
|
||||
}
|
||||
|
||||
public LocalDate getDateInscription() {
|
||||
return dateInscription;
|
||||
}
|
||||
|
||||
public void setDateInscription(LocalDate dateInscription) {
|
||||
this.dateInscription = dateInscription;
|
||||
}
|
||||
|
||||
public boolean isEstVerifie() {
|
||||
return estVerifie;
|
||||
}
|
||||
|
||||
public void setEstVerifie(boolean estVerifie) {
|
||||
this.estVerifie = estVerifie;
|
||||
}
|
||||
|
||||
public boolean isEstBloque() {
|
||||
return estBloque;
|
||||
}
|
||||
|
||||
public void setEstBloque(boolean estBloque) {
|
||||
this.estBloque = estBloque;
|
||||
}
|
||||
|
||||
public double getSoldeCompte() {
|
||||
return soldeCompte;
|
||||
}
|
||||
|
||||
public void setSoldeCompte(double soldeCompte) {
|
||||
this.soldeCompte = soldeCompte;
|
||||
}
|
||||
|
||||
public double getLimiteMise() {
|
||||
return limiteMise;
|
||||
}
|
||||
|
||||
public void setLimiteMise(double limiteMise) {
|
||||
this.limiteMise = limiteMise;
|
||||
}
|
||||
|
||||
public List<Pari> getParis() {
|
||||
return paris;
|
||||
}
|
||||
|
||||
public void setParis(List<Pari> paris) {
|
||||
this.paris = paris;
|
||||
}
|
||||
|
||||
public List<Transaction> getTransactions() {
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public void setTransactions(List<Transaction> transactions) {
|
||||
this.transactions = transactions;
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
public void ajouterPari(Pari pari) {
|
||||
paris.add(pari);
|
||||
pari.setClient(this);
|
||||
}
|
||||
|
||||
public void retirerPari(Pari pari) {
|
||||
paris.remove(pari);
|
||||
pari.setClient(null);
|
||||
}
|
||||
|
||||
public void crediterCompte(double montant) {
|
||||
this.soldeCompte += montant;
|
||||
}
|
||||
|
||||
public void debiterCompte(double montant) {
|
||||
if (this.soldeCompte < montant) {
|
||||
throw new IllegalStateException("Solde insuffisant");
|
||||
}
|
||||
this.soldeCompte -= montant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Client{" +
|
||||
"id=" + id +
|
||||
", nom='" + nom + '\'' +
|
||||
", prenom='" + prenom + '\'' +
|
||||
", numeroClient='" + numeroClient + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
public class Combinaison {
|
||||
private final Cheval cheval1;
|
||||
private final Cheval cheval2;
|
||||
private int nombreMises;
|
||||
|
||||
public Combinaison(Cheval cheval1, Cheval cheval2) {
|
||||
this.cheval1 = cheval1;
|
||||
this.cheval2 = cheval2;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Cheval getCheval1() { return cheval1; }
|
||||
public Cheval getCheval2() { return cheval2; }
|
||||
public int getNombreMises() { return nombreMises; }
|
||||
|
||||
public void setNombreMises(int nombreMises) {
|
||||
this.nombreMises = nombreMises;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Combinaison)) return false;
|
||||
Combinaison that = (Combinaison) o;
|
||||
return (cheval1.equals(that.cheval1) && cheval2.equals(that.cheval2)) ||
|
||||
(cheval1.equals(that.cheval2) && cheval2.equals(that.cheval1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cheval1.hashCode() + cheval2.hashCode(); // Ordre non important
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
|
||||
public class Course {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String nom;
|
||||
private LocalDateTime dateCourse;
|
||||
private boolean estAnnulee;
|
||||
private boolean deadHeat;
|
||||
|
||||
private boolean isTerminee=false;
|
||||
|
||||
public boolean isTerminee() {
|
||||
return isTerminee;
|
||||
}
|
||||
|
||||
public void setTerminee(boolean terminee) {
|
||||
isTerminee = terminee;
|
||||
}
|
||||
|
||||
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
|
||||
private List<Cheval> chevaux = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "course")
|
||||
private List<Pari> paris = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getNom() {
|
||||
return nom;
|
||||
}
|
||||
|
||||
public void setNom(String nom) {
|
||||
this.nom = nom;
|
||||
}
|
||||
|
||||
public LocalDateTime getDateCourse() {
|
||||
return dateCourse;
|
||||
}
|
||||
|
||||
public void setDateCourse(LocalDateTime dateCourse) {
|
||||
this.dateCourse = dateCourse;
|
||||
}
|
||||
|
||||
public boolean isEstAnnulee() {
|
||||
return estAnnulee;
|
||||
}
|
||||
|
||||
public void setEstAnnulee(boolean estAnnulee) {
|
||||
this.estAnnulee = estAnnulee;
|
||||
}
|
||||
|
||||
public boolean isDeadHeat() {
|
||||
return deadHeat;
|
||||
}
|
||||
|
||||
public void setDeadHeat(boolean deadHeat) {
|
||||
this.deadHeat = deadHeat;
|
||||
}
|
||||
|
||||
public List<Cheval> getChevaux() {
|
||||
return chevaux;
|
||||
}
|
||||
|
||||
public void setChevaux(List<Cheval> chevaux) {
|
||||
this.chevaux = chevaux;
|
||||
}
|
||||
|
||||
public List<Pari> getParis() {
|
||||
return paris;
|
||||
}
|
||||
|
||||
public void setParis(List<Pari> paris) {
|
||||
this.paris = paris;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
import com.pmumali.simple.model.enums.TypePari;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
public class Pari {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private double montantMise;
|
||||
private LocalDateTime datePari;
|
||||
private boolean estPaye;
|
||||
private double gains;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Course course;
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "pari_cheval",
|
||||
joinColumns = @JoinColumn(name = "pari_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "cheval_id"))
|
||||
private List<Cheval> chevauxJumeles = new ArrayList<>();
|
||||
|
||||
public TypePari getTypePari() {
|
||||
return typePari;
|
||||
}
|
||||
|
||||
public void setTypePari(TypePari typePari) {
|
||||
this.typePari = typePari;
|
||||
}
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private TypePari typePari; // JUMELEC_GAGNANT ou JUMELEC_PLACE
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public double getMontantMise() {
|
||||
return montantMise;
|
||||
}
|
||||
|
||||
public void setMontantMise(double montantMise) {
|
||||
this.montantMise = montantMise;
|
||||
}
|
||||
|
||||
public LocalDateTime getDatePari() {
|
||||
return datePari;
|
||||
}
|
||||
|
||||
public void setDatePari(LocalDateTime datePari) {
|
||||
this.datePari = datePari;
|
||||
}
|
||||
|
||||
public boolean isEstPaye() {
|
||||
return estPaye;
|
||||
}
|
||||
|
||||
public void setEstPaye(boolean estPaye) {
|
||||
this.estPaye = estPaye;
|
||||
}
|
||||
|
||||
public double getGains() {
|
||||
return gains;
|
||||
}
|
||||
|
||||
public void setGains(double gains) {
|
||||
this.gains = gains;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Course getCourse() {
|
||||
return course;
|
||||
}
|
||||
|
||||
public void setCourse(Course course) {
|
||||
this.course = course;
|
||||
}
|
||||
|
||||
public List<Cheval> getChevauxJumeles() {
|
||||
return chevauxJumeles;
|
||||
}
|
||||
|
||||
public void setChevauxJumeles(List<Cheval> chevauxJumeles) {
|
||||
this.chevauxJumeles = chevauxJumeles;
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "resultats_courses")
|
||||
public class ResultatCourse {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "course_id", nullable = false, unique = true)
|
||||
private Course course;
|
||||
|
||||
@Column(name = "date_publication", nullable = false)
|
||||
private LocalDateTime datePublication = LocalDateTime.now();
|
||||
|
||||
@Column(name = "est_officiel", nullable = false)
|
||||
private boolean estOfficiel = false;
|
||||
|
||||
@Column(name = "masse_a_partager", nullable = false)
|
||||
private double masseAPartager;
|
||||
|
||||
@Column(name = "tirelire_active", nullable = false)
|
||||
private boolean tirelireActive = false;
|
||||
|
||||
@Column(name = "montant_tirelire")
|
||||
private Double montantTirelire;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "resultats_combinaisons", joinColumns = @JoinColumn(name = "resultat_id"))
|
||||
@MapKeyColumn(name = "combinaison")
|
||||
@Column(name = "rapport")
|
||||
private Map<String, Double> rapports = new HashMap<>();
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "resultats_non_partants", joinColumns = @JoinColumn(name = "resultat_id"))
|
||||
@Column(name = "cheval_id")
|
||||
private Set<Long> chevauxNonPartants = new HashSet<>();
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_resultat", nullable = false)
|
||||
private TypeResultat typeResultat;
|
||||
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
// Enumération pour les types de résultats
|
||||
public enum TypeResultat {
|
||||
NORMAL,
|
||||
DEAD_HEAT_PREMIERS,
|
||||
DEAD_HEAT_DEUXIEMES,
|
||||
DEAD_HEAT_TROISIEMES,
|
||||
COURSE_ANNULEE
|
||||
}
|
||||
|
||||
// Constructeurs
|
||||
public ResultatCourse() {}
|
||||
|
||||
public ResultatCourse(Course course) {
|
||||
this.course = course;
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Course getCourse() {
|
||||
return course;
|
||||
}
|
||||
|
||||
public void setCourse(Course course) {
|
||||
this.course = course;
|
||||
}
|
||||
|
||||
public LocalDateTime getDatePublication() {
|
||||
return datePublication;
|
||||
}
|
||||
|
||||
public void setDatePublication(LocalDateTime datePublication) {
|
||||
this.datePublication = datePublication;
|
||||
}
|
||||
|
||||
public boolean isEstOfficiel() {
|
||||
return estOfficiel;
|
||||
}
|
||||
|
||||
public void setEstOfficiel(boolean estOfficiel) {
|
||||
this.estOfficiel = estOfficiel;
|
||||
}
|
||||
|
||||
public double getMasseAPartager() {
|
||||
return masseAPartager;
|
||||
}
|
||||
|
||||
public void setMasseAPartager(double masseAPartager) {
|
||||
this.masseAPartager = masseAPartager;
|
||||
}
|
||||
|
||||
public boolean isTirelireActive() {
|
||||
return tirelireActive;
|
||||
}
|
||||
|
||||
public void setTirelireActive(boolean tirelireActive) {
|
||||
this.tirelireActive = tirelireActive;
|
||||
}
|
||||
|
||||
public Double getMontantTirelire() {
|
||||
return montantTirelire;
|
||||
}
|
||||
|
||||
public void setMontantTirelire(Double montantTirelire) {
|
||||
this.montantTirelire = montantTirelire;
|
||||
}
|
||||
|
||||
public Map<String, Double> getRapports() {
|
||||
return rapports;
|
||||
}
|
||||
|
||||
public void setRapports(Map<String, Double> rapports) {
|
||||
this.rapports = rapports;
|
||||
}
|
||||
|
||||
public Set<Long> getChevauxNonPartants() {
|
||||
return chevauxNonPartants;
|
||||
}
|
||||
|
||||
public void setChevauxNonPartants(Set<Long> chevauxNonPartants) {
|
||||
this.chevauxNonPartants = chevauxNonPartants;
|
||||
}
|
||||
|
||||
public TypeResultat getTypeResultat() {
|
||||
return typeResultat;
|
||||
}
|
||||
|
||||
public void setTypeResultat(TypeResultat typeResultat) {
|
||||
this.typeResultat = typeResultat;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
// Méthodes métiers
|
||||
public void ajouterRapport(String combinaison, double rapport) {
|
||||
this.rapports.put(combinaison, rapport);
|
||||
}
|
||||
|
||||
public void marquerNonPartant(Long chevalId) {
|
||||
this.chevauxNonPartants.add(chevalId);
|
||||
}
|
||||
|
||||
public boolean estCombinaisonPayable(String combinaison) {
|
||||
return this.rapports.containsKey(combinaison);
|
||||
}
|
||||
|
||||
public void activerTirelire(double montant) {
|
||||
this.tirelireActive = true;
|
||||
this.montantTirelire = montant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ResultatCourse)) return false;
|
||||
ResultatCourse that = (ResultatCourse) o;
|
||||
return Objects.equals(id, that.id) &&
|
||||
Objects.equals(course, that.course);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, course);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResultatCourse{" +
|
||||
"id=" + id +
|
||||
", course=" + course.getId() +
|
||||
", typeResultat=" + typeResultat +
|
||||
", rapports=" + rapports.size() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.pmumali.simple.model;
|
||||
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "transactions")
|
||||
public class Transaction {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private double montant;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime dateHeure = LocalDateTime.now();
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
private String type; // DÉPÔT, RETRAIT, GAIN, REMBOURSEMENT
|
||||
|
||||
@Column(length = 100)
|
||||
private String description;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "client_id")
|
||||
private Client client;
|
||||
|
||||
// Getters, setters et constructeurs
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.pmumali.simple.model.enums;
|
||||
|
||||
public enum TypePari {
|
||||
JUMELEC_GAGNANT,
|
||||
JUMELEC_PLACE
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.pmumali.simple.repository;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ChevalRepository extends JpaRepository<Cheval,Long> {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.pmumali.simple.repository;
|
||||
|
||||
import com.pmumali.simple.model.Course;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CourseRepository extends JpaRepository<Course,Long> {
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.pmumali.simple.repository;
|
||||
|
||||
import com.pmumali.simple.model.Pari;
|
||||
import com.pmumali.simple.model.Client;
|
||||
import com.pmumali.simple.model.Course;
|
||||
import com.pmumali.simple.model.enums.TypePari;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface PariRepository extends JpaRepository<Pari, Long> {
|
||||
|
||||
double sumByClientAndCourse(Long clientID, Long courseId);
|
||||
|
||||
// Trouver tous les paris d'un client
|
||||
List<Pari> findByClient(Client client);
|
||||
|
||||
// Trouver les paris par course
|
||||
List<Pari> findByCourse(Course course);
|
||||
|
||||
// Trouver les paris par type (JUMELEC_PLACE ou JUMELEC_GAGNANT)
|
||||
List<Pari> findByTypePari(TypePari typePari);
|
||||
|
||||
// Somme des mises d'un client pour une course (Article 2 - Limitation des enjeux)
|
||||
@Query("SELECT COALESCE(SUM(p.montantMise), 0) FROM Pari p WHERE p.client = :client AND p.course = :course")
|
||||
double sumMisesByClientAndCourse(@Param("client") Client client, @Param("course") Course course);
|
||||
|
||||
// Trouver les paris gagnants pour une combinaison donnée
|
||||
@Query("SELECT p FROM Pari p JOIN p.chevauxJumeles c WHERE p.course = :course " +
|
||||
"AND c.id IN (:cheval1Id, :cheval2Id) GROUP BY p HAVING COUNT(c) = 2")
|
||||
List<Pari> findParisGagnantsForCombinaison(
|
||||
@Param("course") Course course,
|
||||
@Param("cheval1Id") Long cheval1Id,
|
||||
@Param("cheval2Id") Long cheval2Id
|
||||
);
|
||||
|
||||
// Nombre de mises pour une combinaison spécifique
|
||||
@Query("SELECT COUNT(p) FROM Pari p JOIN p.chevauxJumeles c WHERE p.course = :course " +
|
||||
"AND c.id IN (:cheval1Id, :cheval2Id) GROUP BY p HAVING COUNT(c) = 2")
|
||||
long countMisesForCombinaison(
|
||||
@Param("course") Course course,
|
||||
@Param("cheval1Id") Long cheval1Id,
|
||||
@Param("cheval2Id") Long cheval2Id
|
||||
);
|
||||
|
||||
// Trouver les paris contenant un cheval non-partant (Article 4)
|
||||
@Query("SELECT DISTINCT p FROM Pari p JOIN p.chevauxJumeles c WHERE p.course = :course AND c.estNonPartant = true")
|
||||
List<Pari> findParisAvecNonPartants(@Param("course") Course course);
|
||||
|
||||
// Statistiques pour le dashboard
|
||||
@Query("SELECT NEW map(p.typePari as type, COUNT(p) as nombre, SUM(p.montantMise) as totalMises) " +
|
||||
"FROM Pari p WHERE p.datePari BETWEEN :start AND :end GROUP BY p.typePari")
|
||||
List<Map<String, Object>> getStatistiquesParType(
|
||||
@Param("start") LocalDateTime startDate,
|
||||
@Param("end") LocalDateTime endDate
|
||||
);
|
||||
|
||||
// Trouver les paris non payés pour une course
|
||||
List<Pari> findByCourseAndEstPayeFalse(Course course);
|
||||
|
||||
// Méthode optimisée pour le calcul des rapports
|
||||
@Query("SELECT NEW map(c1.id as cheval1Id, c2.id as cheval2Id, COUNT(p) as mises) " +
|
||||
"FROM Pari p JOIN p.chevauxJumeles c1 JOIN p.chevauxJumeles c2 " +
|
||||
"WHERE p.course = :course AND c1.id < c2.id " +
|
||||
"GROUP BY c1.id, c2.id")
|
||||
List<Map<String, Object>> getMisesParCombinaison(@Param("course") Course course);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package com.pmumali.simple.service;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import com.pmumali.simple.model.Combinaison;
|
||||
import com.pmumali.simple.model.Course;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CalculRapportService {
|
||||
|
||||
public Map<Combinaison, Double> calculerRapports(
|
||||
List<Combinaison> combinaisonsPayables,
|
||||
double masseAPartager) {
|
||||
|
||||
Map<Combinaison, Double> rapports = new HashMap<>();
|
||||
|
||||
if (combinaisonsPayables.size() == 1) {
|
||||
// Cas arrivée normale
|
||||
double rapport = masseAPartager / combinaisonsPayables.get(0).getNombreMises();
|
||||
rapports.put(combinaisonsPayables.get(0), Math.max(1.1, rapport));
|
||||
} else {
|
||||
// Cas Dead-Heat
|
||||
double beneficeParCombinaison = masseAPartager / combinaisonsPayables.size();
|
||||
|
||||
for (Combinaison combinaison : combinaisonsPayables) {
|
||||
double rapport = beneficeParCombinaison / combinaison.getNombreMises();
|
||||
rapports.put(combinaison, Math.max(1.1, rapport));
|
||||
}
|
||||
}
|
||||
|
||||
return rapports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une combinaison est gagnante selon les positions d'arrivée
|
||||
*/
|
||||
public boolean isCombinaisonGagnante(Combinaison combinaison, List<Cheval> classement) {
|
||||
if (classement.size() < 2) return false;
|
||||
|
||||
Cheval c1 = combinaison.getCheval1();
|
||||
Cheval c2 = combinaison.getCheval2();
|
||||
|
||||
// Les deux chevaux doivent être dans les 2 premiers (ordre quelconque)
|
||||
return classement.subList(0, 2).contains(c1) &&
|
||||
classement.subList(0, 2).contains(c2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le nombre de mises pour une combinaison donnée
|
||||
*/
|
||||
public long compterMisesCombinaison(Course course, Combinaison combinaison) {
|
||||
return course.getParis().stream()
|
||||
.filter(p -> p.getChevauxJumeles().containsAll(
|
||||
List.of(combinaison.getCheval1(), combinaison.getCheval2())
|
||||
))
|
||||
.count();
|
||||
}
|
||||
|
||||
|
||||
public Map<Combinaison, Double> calculerRapportsJumelePlace(
|
||||
List<Combinaison> combinaisonsPayables,
|
||||
double masseAPartager) {
|
||||
|
||||
Map<Combinaison, Double> rapports = new HashMap<>();
|
||||
|
||||
if (combinaisonsPayables.isEmpty()) {
|
||||
return rapports;
|
||||
}
|
||||
|
||||
// Article 5a: Arrivée normale - division en 3 parts égales
|
||||
if (!combinaisonsPayables.get(0).getCourse().isDeadHeat()) {
|
||||
double part = masseAPartager / 3;
|
||||
|
||||
for (Combinaison combinaison : combinaisonsPayables) {
|
||||
double rapport = part / compterMisesCombinaison(combinaison);
|
||||
rapports.put(combinaison, Math.max(RAPPORT_MINIMUM, rapport));
|
||||
}
|
||||
|
||||
return rapports;
|
||||
}
|
||||
|
||||
// Article 5b: Dead-Heat
|
||||
Course course = combinaisonsPayables.get(0).getCourse();
|
||||
Map<Integer, List<Cheval>> parPosition = course.getChevaux().stream()
|
||||
.filter(c -> !c.isEstNonPartant())
|
||||
.collect(Collectors.groupingBy(Cheval::getPositionArrivee));
|
||||
|
||||
List<Cheval> premiers = parPosition.getOrDefault(1, Collections.emptyList());
|
||||
List<Cheval> deuxiemes = parPosition.getOrDefault(2, Collections.emptyList());
|
||||
List<Cheval> troisiemes = parPosition.getOrDefault(3, Collections.emptyList());
|
||||
|
||||
// Article 5b1: Dead-Heat 3+ premiers
|
||||
if (premiers.size() >= 3) {
|
||||
double part = masseAPartager / combinaisonsPayables.size();
|
||||
for (Combinaison combinaison : combinaisonsPayables) {
|
||||
double rapport = part / compterMisesCombinaison(combinaison);
|
||||
rapports.put(combinaison, Math.max(RAPPORT_MINIMUM, rapport));
|
||||
}
|
||||
return rapports;
|
||||
}
|
||||
|
||||
// Article 5b2: Dead-Heat 2 premiers + 1+ troisième
|
||||
if (premiers.size() == 2 && !troisiemes.isEmpty()) {
|
||||
// Répartition en 3 tiers
|
||||
double tiers = masseAPartager / 3;
|
||||
|
||||
// 1er tiers: combinaison des 2 premiers
|
||||
Combinaison combinaisonPremiers = new Combinaison(premiers.get(0), premiers.get(1));
|
||||
double rapportPremiers = tiers / compterMisesCombinaison(combinaisonPremiers);
|
||||
rapports.put(combinaisonPremiers, Math.max(RAPPORT_MINIMUM, rapportPremiers));
|
||||
|
||||
// 2ème tiers: premier1 avec troisièmes
|
||||
double partParCombinaison = tiers / troisiemes.size();
|
||||
for (Cheval troisieme : troisiemes) {
|
||||
Combinaison combinaison = new Combinaison(premiers.get(0), troisieme);
|
||||
double rapport = partParCombinaison / compterMisesCombinaison(combinaison);
|
||||
rapports.put(combinaison, Math.max(RAPPORT_MINIMUM, rapport));
|
||||
}
|
||||
|
||||
// 3ème tiers: premier2 avec troisièmes
|
||||
for (Cheval troisieme : troisiemes) {
|
||||
Combinaison combinaison = new Combinaison(premiers.get(1), troisieme);
|
||||
double rapport = partParCombinaison / compterMisesCombinaison(combinaison);
|
||||
rapports.put(combinaison, Math.max(RAPPORT_MINIMUM, rapport));
|
||||
}
|
||||
|
||||
return rapports;
|
||||
}
|
||||
|
||||
// ... autres cas Dead-Heat (implémenter de manière similaire)
|
||||
|
||||
return rapports;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.pmumali.simple.service;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import com.pmumali.simple.model.Combinaison;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class FormulaireService {
|
||||
|
||||
public double calculerCoutFormule(int nombreChevaux, boolean formuleComplete) {
|
||||
// Article 7: Tableaux des combinaisons
|
||||
int nbCombinaisons = formuleComplete ?
|
||||
nombreChevaux * (nombreChevaux - 1) / 2 :
|
||||
nombreChevaux;
|
||||
|
||||
return nbCombinaisons * 500; // 500 FCFA par combinaison
|
||||
}
|
||||
|
||||
public List<Combinaison> genererCombinaisonsFormule(
|
||||
List<Cheval> chevauxSelectionnes,
|
||||
boolean formuleComplete) {
|
||||
|
||||
List<Combinaison> combinaisons = new ArrayList<>();
|
||||
|
||||
if (formuleComplete) {
|
||||
// Toutes les combinaisons 2 à 2
|
||||
for (int i = 0; i < chevauxSelectionnes.size(); i++) {
|
||||
for (int j = i + 1; j < chevauxSelectionnes.size(); j++) {
|
||||
combinaisons.add(new Combinaison(
|
||||
chevauxSelectionnes.get(i),
|
||||
chevauxSelectionnes.get(j)
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Formule simplifiée (champ total/partiel)
|
||||
Cheval base = chevauxSelectionnes.get(0);
|
||||
for (int i = 1; i < chevauxSelectionnes.size(); i++) {
|
||||
combinaisons.add(new Combinaison(base, chevauxSelectionnes.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
return combinaisons;
|
||||
}
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
package com.pmumali.simple.service;
|
||||
|
||||
import com.pmumali.simple.dto.CombinaisonDto;
|
||||
import com.pmumali.model.*;
|
||||
import com.pmumali.simple.repository.ChevalRepository;
|
||||
import com.pmumali.simple.repository.CourseRepository;
|
||||
import com.pmumali.simple.repository.PariRepository;
|
||||
import com.pmumali.simple.model.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class PariService {
|
||||
|
||||
@Autowired
|
||||
private PariRepository pariRepository;
|
||||
|
||||
@Autowired
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
@Autowired
|
||||
private ChevalRepository chevalRepository;
|
||||
|
||||
|
||||
public Pari placerPariJumele(Long clientId, Long courseId,
|
||||
List<Long> chevauxIds, double montant) {
|
||||
// Vérification mise minimum
|
||||
if (montant < 500) {
|
||||
throw new IllegalArgumentException("La mise minimale est de 500 FCFA");
|
||||
}
|
||||
|
||||
// Vérification limite de paris (20x mise min = 10 000 FCFA)
|
||||
double totalParisClient = pariRepository
|
||||
.sumByClientAndCourse(clientId, courseId);
|
||||
|
||||
if (totalParisClient + montant > 10000) {
|
||||
throw new IllegalArgumentException("Limite de mise dépassée");
|
||||
}
|
||||
|
||||
// Création du pari
|
||||
Pari pari = new Pari();
|
||||
// ... initialisation
|
||||
|
||||
return pariRepository.save(pari);
|
||||
}
|
||||
|
||||
public ResultatCourse calculerResultats(Long courseId) throws Exception {
|
||||
Course course = courseRepository.findById(courseId)
|
||||
.orElseThrow(() -> new Exception("Course non trouvée"));
|
||||
|
||||
// Implémentation des règles de calcul
|
||||
return calculerResultatsSelonReglement(course);
|
||||
}
|
||||
|
||||
private ResultatCourse calculerResultatsSelonReglement(Course course) {
|
||||
ResultatCourse resultat = new ResultatCourse();
|
||||
|
||||
// 1. Vérifier les non-partants (Article 4)
|
||||
List<Cheval> nonPartants = course.getChevaux().stream()
|
||||
.filter(Cheval::isEstNonPartant)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 2. Calcul selon Dead-Heat (Article 3)
|
||||
if (course.isDeadHeat()) {
|
||||
calculerDeadHeat(resultat, course);
|
||||
} else {
|
||||
calculerArriveeNormale(resultat, course);
|
||||
}
|
||||
|
||||
// 3. Appliquer tirelire si nécessaire (Article 9)
|
||||
if (resultat.getCombinaisonsPayables().isEmpty()) {
|
||||
resultat.setTirelire(true);
|
||||
}
|
||||
|
||||
return resultat;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Détermine les combinaisons payables selon les règles du PMU Mali
|
||||
*/
|
||||
private List<Combinaison> determinerCombinaisonsPayables(Course course) {
|
||||
List<Cheval> chevauxArrives = course.getChevaux().stream()
|
||||
.filter(c -> !c.isEstNonPartant())
|
||||
.sorted(Comparator.comparingInt(Cheval::getPositionArrivee))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Article 4: Remboursement si non-partant
|
||||
if (course.getChevaux().stream().anyMatch(Cheval::isEstNonPartant)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Article 3: Gestion Dead-Heat
|
||||
if (course.isDeadHeat()) {
|
||||
return calculerCombinaisonsDeadHeat(chevauxArrives);
|
||||
}
|
||||
|
||||
// Article 5: Arrivée normale
|
||||
return calculerCombinaisonsNormales(chevauxArrives);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implémentation de l'article 3 - Cas Dead-Heat
|
||||
*/
|
||||
private List<Combinaison> calculerCombinaisonsDeadHeat(List<Cheval> chevauxArrives) {
|
||||
List<Combinaison> combinaisons = new ArrayList<>();
|
||||
|
||||
// Groupes par position d'arrivée
|
||||
Map<Integer, List<Cheval>> parPosition = chevauxArrives.stream()
|
||||
.collect(Collectors.groupingBy(Cheval::getPositionArrivee));
|
||||
|
||||
List<Cheval> premiers = parPosition.getOrDefault(1, Collections.emptyList());
|
||||
List<Cheval> deuxiemes = parPosition.getOrDefault(2, Collections.emptyList());
|
||||
|
||||
// Cas Dead-Heat premier place (Article 3a)
|
||||
if (premiers.size() >= 2) {
|
||||
for (int i = 0; i < premiers.size(); i++) {
|
||||
for (int j = i + 1; j < premiers.size(); j++) {
|
||||
combinaisons.add(new Combinaison(premiers.get(i), premiers.get(j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cas Dead-Heat deuxième place (Article 3b)
|
||||
else if (deuxiemes.size() >= 2) {
|
||||
Cheval premier = premiers.get(0);
|
||||
for (Cheval deuxieme : deuxiemes) {
|
||||
combinaisons.add(new Combinaison(premier, deuxieme));
|
||||
}
|
||||
}
|
||||
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implémentation de l'article 5 - Arrivée normale
|
||||
*/
|
||||
private List<Combinaison> calculerCombinaisonsNormales(List<Cheval> chevauxArrives) {
|
||||
if (chevauxArrives.size() < 2) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Cheval premier = chevauxArrives.get(0);
|
||||
Cheval deuxieme = chevauxArrives.get(1);
|
||||
|
||||
return List.of(new Combinaison(premier, deuxieme));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule la masse à partager selon l'article 5
|
||||
*/
|
||||
private double calculerMasseAPartager(Course course, List<Combinaison> combinaisonsPayables) {
|
||||
double totalEnjeux = course.getParis().stream()
|
||||
.mapToDouble(Pari::getMontantMise)
|
||||
.sum();
|
||||
|
||||
double montantRembourse = course.getParis().stream()
|
||||
.filter(p -> p.getChevauxJumeles().stream().anyMatch(Cheval::isEstNonPartant))
|
||||
.mapToDouble(Pari::getMontantMise)
|
||||
.sum();
|
||||
|
||||
// Article 5: MAP = RNET - MREMB - PRELEV
|
||||
double prelevementsLegaux = totalEnjeux * 0.15; // Exemple: 15% de prélèvement
|
||||
return totalEnjeux - montantRembourse - prelevementsLegaux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit les combinaisons en DTO pour la réponse API
|
||||
*/
|
||||
private List<CombinaisonDto> convertToDto(List<Combinaison> combinaisons) {
|
||||
return combinaisons.stream()
|
||||
.map(c -> new CombinaisonDto(
|
||||
c.getCheval1().getNom(),
|
||||
c.getCheval2().getNom(),
|
||||
c.getNombreMises()
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit les rapports en format lisible
|
||||
*/
|
||||
private Map<String, Double> convertRapports(Map<Combinaison, Double> rapports) {
|
||||
return rapports.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
e -> e.getKey().getCheval1().getNom() + "-" + e.getKey().getCheval2().getNom(),
|
||||
Map.Entry::getValue
|
||||
));
|
||||
}
|
||||
|
||||
private List<Combinaison> determinerCombinaisonsPayablesJumelePlace(Course course) {
|
||||
List<Cheval> chevauxArrives = course.getChevaux().stream()
|
||||
.filter(c -> !c.isEstNonPartant())
|
||||
.sorted(Comparator.comparingInt(Cheval::getPositionArrivee))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Article 4: Remboursement si non-partant
|
||||
if (course.getChevaux().stream().anyMatch(Cheval::isEstNonPartant)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Article 8: Moins de 3 chevaux arrivés
|
||||
if (chevauxArrives.size() < 3) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Article 3: Gestion Dead-Heat
|
||||
if (course.isDeadHeat()) {
|
||||
return calculerCombinaisonsDeadHeatJumelePlace(chevauxArrives);
|
||||
}
|
||||
|
||||
// Cas normal - Article 1
|
||||
return calculerCombinaisonsNormalesJumelePlace(chevauxArrives);
|
||||
}
|
||||
|
||||
private List<Combinaison> calculerCombinaisonsNormalesJumelePlace(List<Cheval> chevauxArrives) {
|
||||
List<Combinaison> combinaisons = new ArrayList<>();
|
||||
Cheval premier = chevauxArrives.get(0);
|
||||
Cheval deuxieme = chevauxArrives.get(1);
|
||||
Cheval troisieme = chevauxArrives.get(2);
|
||||
|
||||
// Toutes les combinaisons 2 parmi 3
|
||||
combinaisons.add(new Combinaison(premier, deuxieme));
|
||||
combinaisons.add(new Combinaison(premier, troisieme));
|
||||
combinaisons.add(new Combinaison(deuxieme, troisieme));
|
||||
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
private List<Combinaison> calculerCombinaisonsDeadHeatJumelePlace(List<Cheval> chevauxArrives) {
|
||||
List<Combinaison> combinaisons = new ArrayList<>();
|
||||
Map<Integer, List<Cheval>> parPosition = chevauxArrives.stream()
|
||||
.collect(Collectors.groupingBy(Cheval::getPositionArrivee));
|
||||
|
||||
List<Cheval> premiers = parPosition.getOrDefault(1, Collections.emptyList());
|
||||
List<Cheval> deuxiemes = parPosition.getOrDefault(2, Collections.emptyList());
|
||||
List<Cheval> troisiemes = parPosition.getOrDefault(3, Collections.emptyList());
|
||||
|
||||
// Article 3a: Dead-Heat à la première place (3+ chevaux)
|
||||
if (premiers.size() >= 3) {
|
||||
for (int i = 0; i < premiers.size(); i++) {
|
||||
for (int j = i + 1; j < premiers.size(); j++) {
|
||||
combinaisons.add(new Combinaison(premiers.get(i), premiers.get(j)));
|
||||
}
|
||||
}
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
// Article 3b: Dead-Heat 2 premiers + 1+ troisième
|
||||
if (premiers.size() == 2 && !troisiemes.isEmpty()) {
|
||||
// Combinaison des deux premiers
|
||||
combinaisons.add(new Combinaison(premiers.get(0), premiers.get(1)));
|
||||
|
||||
// Combinaisons premier1 avec troisièmes
|
||||
for (Cheval troisieme : troisiemes) {
|
||||
combinaisons.add(new Combinaison(premiers.get(0), troisieme));
|
||||
}
|
||||
|
||||
// Combinaisons premier2 avec troisièmes
|
||||
for (Cheval troisieme : troisiemes) {
|
||||
combinaisons.add(new Combinaison(premiers.get(1), troisieme));
|
||||
}
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
// Article 3c: Dead-Heat à la deuxième place
|
||||
if (!deuxiemes.isEmpty() && deuxiemes.size() >= 2) {
|
||||
Cheval premier = premiers.get(0);
|
||||
|
||||
// Combinaisons premier avec deuxièmes
|
||||
for (Cheval deuxieme : deuxiemes) {
|
||||
combinaisons.add(new Combinaison(premier, deuxieme));
|
||||
}
|
||||
|
||||
// Combinaisons deuxièmes entre eux
|
||||
for (int i = 0; i < deuxiemes.size(); i++) {
|
||||
for (int j = i + 1; j < deuxiemes.size(); j++) {
|
||||
combinaisons.add(new Combinaison(deuxiemes.get(i), deuxiemes.get(j)));
|
||||
}
|
||||
}
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
// Article 3d: Dead-Heat à la troisième place
|
||||
if (!troisiemes.isEmpty() && troisiemes.size() >= 2) {
|
||||
Cheval premier = premiers.get(0);
|
||||
Cheval deuxieme = deuxiemes.get(0);
|
||||
|
||||
// Combinaison premier-deuxième
|
||||
combinaisons.add(new Combinaison(premier, deuxieme));
|
||||
|
||||
// Combinaisons premier avec troisièmes
|
||||
for (Cheval troisieme : troisiemes) {
|
||||
combinaisons.add(new Combinaison(premier, troisieme));
|
||||
}
|
||||
|
||||
// Combinaisons deuxième avec troisièmes
|
||||
for (Cheval troisieme : troisiemes) {
|
||||
combinaisons.add(new Combinaison(deuxieme, troisieme));
|
||||
}
|
||||
return combinaisons;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.pmumali.simple.service;
|
||||
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import com.pmumali.simple.model.Combinaison;
|
||||
import com.pmumali.simple.model.Course;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PariServiceTest {
|
||||
|
||||
@Test
|
||||
public void testCombinaisonsPayables_Normal() {
|
||||
Course course = new Course();
|
||||
course.setDeadHeat(false);
|
||||
|
||||
Cheval c1 = new Cheval(); c1.setPositionArrivee(1);
|
||||
Cheval c2 = new Cheval(); c2.setPositionArrivee(2);
|
||||
Cheval c3 = new Cheval(); c3.setPositionArrivee(3);
|
||||
course.setChevaux(List.of(c1, c2, c3));
|
||||
|
||||
List<Combinaison> result = pariService.determinerCombinaisonsPayablesJumelePlace(course);
|
||||
|
||||
assertEquals(3, result.size());
|
||||
assertTrue(result.contains(new Combinaison(c1, c2)));
|
||||
assertTrue(result.contains(new Combinaison(c1, c3)));
|
||||
assertTrue(result.contains(new Combinaison(c2, c3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeadHeat_TroisPremiers() {
|
||||
Course course = new Course();
|
||||
course.setDeadHeat(true);
|
||||
|
||||
Cheval c1 = new Cheval(); c1.setPositionArrivee(1);
|
||||
Cheval c2 = new Cheval(); c2.setPositionArrivee(1);
|
||||
Cheval c3 = new Cheval(); c3.setPositionArrivee(1);
|
||||
course.setChevaux(List.of(c1, c2, c3));
|
||||
|
||||
List<Combinaison> result = pariService.determinerCombinaisonsPayablesJumelePlace(course);
|
||||
|
||||
assertEquals(3, result.size()); // C(3,2) = 3 combinaisons
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package com.pmumali.simple.service;
|
||||
|
||||
import com.pmumali.simple.dto.PariRequest;
|
||||
import com.pmumali.simple.exception.PariException;
|
||||
import com.pmumali.simple.model.Cheval;
|
||||
import com.pmumali.simple.model.Client;
|
||||
import com.pmumali.simple.model.Course;
|
||||
import com.pmumali.simple.model.Pari;
|
||||
import com.pmumali.simple.model.enums.TypePari;
|
||||
import com.pmumali.simple.repository.PariRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ValidationPariService {
|
||||
@Autowired
|
||||
private PariRepository pariRepository;
|
||||
|
||||
public void validerPari(Pari pari) {
|
||||
// Article 2: Limitation des enjeux
|
||||
validerLimiteEnjeux(pari);
|
||||
|
||||
// Article 4: Chevaux non-partants
|
||||
validerChevauxPartants(pari.getCourse(), pari.getChevauxJumeles());
|
||||
|
||||
// Article 6: Formules combinées
|
||||
validerFormule(pari);
|
||||
}
|
||||
|
||||
private void validerLimiteEnjeux(Pari pari) {
|
||||
double totalMises = pariRepository
|
||||
.sumByClientAndCourse(pari.getClient().getId(), pari.getCourse().getId());
|
||||
|
||||
if (totalMises + pari.getMontantMise() > 10000) {
|
||||
throw new PariException(PariException.ErrorCode.LIMITE_MISE_DEPASSEE, "Limite de mise dépassée (20x500 FCFA maximum)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide la requête de pari selon les règles métier
|
||||
*/
|
||||
public void validerPariRequest(PariRequest request) {
|
||||
if (request.getMontantMise() < 500) {
|
||||
throw new PariException(PariException.ErrorCode.MISE_MINIMALE_NON_ATTEINTE,"La mise minimale est de 500 FCFA (Article 1)");
|
||||
}
|
||||
|
||||
if (request.getChevauxIds() == null || request.getChevauxIds().size() != 2) {
|
||||
throw new PariException(PariException.ErrorCode.PARI_INVALIDE,"Un pari Jumelé Gagnant doit porter sur exactement 2 chevaux");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide le pari selon toutes les règles du règlement
|
||||
*/
|
||||
public void validerPari(Client client, Course course, List<Cheval> chevaux, double montantMise) {
|
||||
// Article 2: Limitation des enjeux
|
||||
validerLimiteEnjeux(client, course, montantMise);
|
||||
|
||||
// Article 4: Chevaux non-partants
|
||||
validerChevauxPartants(course, chevaux);
|
||||
|
||||
// Article 10: Course non annulée
|
||||
if (course.isEstAnnulee()) {
|
||||
throw new PariException(PariException.ErrorCode.COURSE_ANNULEE,"Course annulée - tous les paris seront remboursés (Article 8)");
|
||||
}
|
||||
|
||||
// Vérifie que les chevaux appartiennent bien à la course
|
||||
if (chevaux.stream().anyMatch(c -> !c.getCourse().equals(course))) {
|
||||
throw new PariException(PariException.ErrorCode.CHEVAL_NON_PARTANT,"Un ou plusieurs chevaux ne font pas partie de cette course");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implémentation de l'article 2 - Limitation des enjeux
|
||||
*/
|
||||
private void validerLimiteEnjeux(Client client, Course course, double nouvelleMise) {
|
||||
double totalMises = pariRepository.sumByClientAndCourse(client.getId(), course.getId());
|
||||
double limite = 20 * 500; // 20 fois la mise de base (500 FCFA)
|
||||
|
||||
if (totalMises + nouvelleMise > limite) {
|
||||
throw new PariException( PariException.ErrorCode.LIMITE_MISE_DEPASSEE,
|
||||
String.format("Limite de mise dépassée (max %,.0f FCFA par course selon Article 2)", limite)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implémentation de l'article 4 - Chevaux non-partants
|
||||
*/
|
||||
private void validerChevauxPartants(Course course, List<Cheval> chevaux) {
|
||||
if (chevaux.stream().anyMatch(Cheval::isEstNonPartant)) {
|
||||
throw new PariException(PariException.ErrorCode.CHEVAL_NON_PARTANT,
|
||||
"Pari non valide : un ou plusieurs chevaux sont non-partants (Article 4)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void validerPariJumelePlace(Pari pari) {
|
||||
// Article 1: Vérification que c'est bien un Jumelé Placé
|
||||
if (pari.getTypePari() != TypePari.JUMELEC_PLACE) {
|
||||
throw new PariException(PariException.ErrorCode.FORMULE_INVALIDE ,"Ce n'est pas un pari Jumelé Placé");
|
||||
}
|
||||
|
||||
// Article 2: Limitation des enjeux (identique)
|
||||
validerLimiteEnjeux(pari.getClient(), pari.getCourse(), pari.getMontantMise());
|
||||
|
||||
// Article 4: Chevaux non-partants
|
||||
if (pari.getChevauxJumeles().stream().anyMatch(Cheval::isEstNonPartant)) {
|
||||
throw new PariException(PariException.ErrorCode.CHEVAL_NON_PARTANT,"Pari non valide: cheval non-partant (Article 4)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user